123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301 |
- /*
- * Copyright 2005 - 2016 Zarafa and its licensors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- #include <kopano/platform.h>
- #include <exception>
- #include <set>
- #include <stdexcept>
- #include <string>
- #include <list>
- #include <map>
- #include <utility>
- #include <mapidefs.h>
- #include <mapitags.h>
- #include <kopano/mapiext.h>
- #include <kopano/memory.hpp>
- #include <kopano/EMSAbTag.h>
- #include <edkmdb.h>
- #include "ECMAPI.h"
- #include "soapH.h"
- #include "ECSessionManager.h"
- #include "ECSecurity.h"
- #include "ics.h"
- #include "ECICS.h"
- #include "StorageUtil.h"
- #include "ECAttachmentStorage.h"
- #include "ECStatsCollector.h"
- #include "ECStringCompat.h"
- #include "ECTPropsPurge.h"
- #include "cmdutil.hpp"
- #define FIELD_NR_NAMEID (FIELD_NR_MAX + 1)
- #define FIELD_NR_NAMESTR (FIELD_NR_MAX + 2)
- #define FIELD_NR_NAMEGUID (FIELD_NR_MAX + 3)
- using namespace KCHL;
- namespace KC {
- extern ECSessionManager* g_lpSessionManager; // FIXME: remove this global and change the depended source code!
- extern ECStatsCollector* g_lpStatsCollector;
- ECRESULT GetSourceKey(unsigned int ulObjId, SOURCEKEY *lpSourceKey)
- {
- ECRESULT er = erSuccess;
- unsigned char *lpData = NULL;
- unsigned int cbData = 0;
- er = g_lpSessionManager->GetCacheManager()->GetPropFromObject(PROP_ID(PR_SOURCE_KEY), ulObjId, NULL, &cbData, &lpData);
- if (er == erSuccess)
- *lpSourceKey = SOURCEKEY(cbData, reinterpret_cast<const char *>(lpData));
- s_free(nullptr, lpData);
- return er;
- }
- /*
- * This is a generic delete function that is called from
- *
- * ns__deleteObjects
- * ns__emptyFolder
- * ns__deleteFolder
- * purgeSoftDelete
- *
- * Functions which using sub set of the delete system are:
- * ns__saveObject
- * importMessageFromStream
- *
- * It does a recursive delete of objects in the hierarchytable, according to the flags given
- * which is any combination of
- *
- * EC_DELETE_FOLDERS - Delete subfolders
- * EC_DELETE_MESSAGES - Delete messages
- * EC_DELETE_RECIPIENTS - Delete recipients of messages
- * EC_DELETE_ATTACHMENTS - Delete attachments of messages
- * EC_DELETE_CONTAINER - Delete the container specified in the first place (otherwise only subobjects)
- *
- * This is done by first recusively retrieving the object IDs, then checking the types of objects in that
- * list. If there is any subobject that has *not* been specified for deletion, the function fails. Else,
- * all properties in the subobjects and the subobjects themselves are deleted. If EC_DELETE_CONTAINER
- * is specified, then the objects passed in lpEntryList are also deleted (together with their properties).
- *
- */
- /**
- * Validate permissions and match object type
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] bCheckPermission Check if the object folder or message has delete permissions.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] sItem Reference to a DELETEITEM structure that contains object information which identifying the folder, message, reciptient and attachment.
- *
- * @return Kopano error code
- */
- ECRESULT ValidateDeleteObject(ECSession *lpSession, bool bCheckPermission, unsigned int ulFlags, const DELETEITEM &sItem)
- {
- ECRESULT er;
- if (lpSession == NULL)
- return KCERR_INVALID_PARAMETER;
- // Check permission for each folder and messages
- if (bCheckPermission && ((sItem.ulObjType == MAPI_MESSAGE && sItem.ulParentType == MAPI_FOLDER) || sItem.ulObjType == MAPI_FOLDER)) {
- er = lpSession->GetSecurity()->CheckPermission(sItem.ulId, ecSecurityDelete);
- if(er != erSuccess)
- return er;
- }
- if (sItem.fRoot == true)
- return erSuccess; // Not for a root
- switch(RealObjType(sItem.ulObjType, sItem.ulParentType)) {
- case MAPI_MESSAGE:
- if (!(ulFlags & EC_DELETE_MESSAGES))
- return KCERR_HAS_MESSAGES;
- break;
- case MAPI_FOLDER:
- if (!(ulFlags & EC_DELETE_FOLDERS))
- return KCERR_HAS_FOLDERS;
- break;
- case MAPI_MAILUSER:
- case MAPI_DISTLIST:
- if (!(ulFlags & EC_DELETE_RECIPIENTS))
- return KCERR_HAS_RECIPIENTS;
- break;
- case MAPI_ATTACH:
- if (!(ulFlags & EC_DELETE_ATTACHMENTS))
- return KCERR_HAS_ATTACHMENTS;
- break;
- case MAPI_STORE: // only admins can delete a store, rights checked in ns__removeStore
- if (!(ulFlags & EC_DELETE_STORE))
- return KCERR_NOT_FOUND;
- break;
- default:
- // Unknown object type? We'll delete it anyway so we don't get frustrating non-deletable items
- assert(false); // Only frustrating developers!
- break;
- }
- return erSuccess;
- }
- /**
- * Expand a list of objects, validate permissions and object types
- * This function returns a list of all items that need to be
- * deleted. This may or may not include the given list in
- * lpsObjectList, because of EC_DELETE_CONTAINER.
- *
- * If the ulFLags includes EC_DELETE_NOT_ASSOCIATED_MSG only the associated messages for the container folder is
- * not deleted. If ulFlags EC_DELETE_CONTAINER included, the EC_DELETE_NOT_ASSOCIATED_MSG flag will be ignored.
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] lpDatabase Reference to a database object; cannot be NULL.
- * @param[in] lpsObjectList Reference to a list of objects that contains itemid and must be expanded.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] bCheckPermission Check the objects delete permissions.
- * @param[out] lplstDeleteItems Recursive list with objects
- *
- * @return Kopano error code
- */
- ECRESULT ExpandDeletedItems(ECSession *lpSession, ECDatabase *lpDatabase, ECListInt *lpsObjectList, unsigned int ulFlags, bool bCheckPermission, ECListDeleteItems *lplstDeleteItems)
- {
- ECRESULT er = erSuccess;
-
- ECListIntIterator iListObjectId;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- std::string strQuery;
- std::set<unsigned int> setIDs;
- ECListDeleteItems lstDeleteItems;
- ECListDeleteItems lstContainerItems;
- DELETEITEM sItem;
- ECSessionManager *lpSessionManager = NULL;
- ECCacheManager *lpCacheManager = NULL;
- unsigned int ulParent = 0;
-
- if (lpSession == NULL || lpDatabase == NULL || lpsObjectList == NULL || lplstDeleteItems == NULL) {
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- lpSessionManager = lpSession->GetSessionManager();
- lpCacheManager = lpSessionManager->GetCacheManager();
- // First, put all the root objects in the list
- for (iListObjectId = lpsObjectList->begin();
- iListObjectId != lpsObjectList->end(); ++iListObjectId)
- {
- sItem.fRoot = true;
- // Lock the root records's parent counter to maintain locking order (counters/content/storesize/committimemax)
- er = lpCacheManager->GetObject(*iListObjectId, &ulParent, NULL, NULL, NULL);
- if(er != erSuccess)
- goto exit;
-
- er = lpDatabase->DoSelect("SELECT properties.val_ulong FROM properties WHERE hierarchyid = " + stringify(ulParent) + " FOR UPDATE", NULL);
- if(er != erSuccess)
- goto exit;
- // Lock the root records to make sure that we don't interfere with modifies or deletes on the same record
- er = lpDatabase->DoSelect("SELECT hierarchy.flags FROM hierarchy WHERE id = " + stringify(*iListObjectId) + " FOR UPDATE", NULL);
- if(er != erSuccess)
- goto exit;
-
- 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 ";
- if((ulFlags & EC_DELETE_CONTAINER) == 0)
- strQuery += "h.parent=" + stringify(*iListObjectId);
- else
- strQuery += "h.id=" + stringify(*iListObjectId);
- if((ulFlags & EC_DELETE_HARD_DELETE) != EC_DELETE_HARD_DELETE)
- strQuery += " AND (h.flags&"+stringify(MSGFLAG_DELETED)+") !="+stringify(MSGFLAG_DELETED);
- if ((ulFlags & (EC_DELETE_CONTAINER | EC_DELETE_NOT_ASSOCIATED_MSG)) == EC_DELETE_NOT_ASSOCIATED_MSG)
- strQuery += " AND (h.flags&"+stringify(MSGFLAG_ASSOCIATED)+") !="+stringify(MSGFLAG_ASSOCIATED);
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- goto exit;
- while ( (lpDBRow = lpDatabase->FetchRow(lpDBResult)) )
- {
- // No type or flags exist
- if(lpDBRow[2] == NULL || lpDBRow[3] == NULL) {
- //er = KCERR_DATABASE_ERROR;
- //goto exit;
- continue;
- }
- // When you delete a store the parent id is NULL, object type must be MAPI_STORE
- if(lpDBRow[1] == NULL && atoi(lpDBRow[2]) != MAPI_STORE) {
- //er = KCERR_DATABASE_ERROR;
- //goto exit;
- continue;
- }
- // Loop protection, don't insert duplicates.
- if (setIDs.insert(atoui(lpDBRow[0])).second == false)
- continue;
-
- sItem.ulId = atoui(lpDBRow[0]);
- sItem.ulParent = (lpDBRow[1])?atoui(lpDBRow[1]) : 0;
- sItem.ulObjType = atoi(lpDBRow[2]);
- sItem.ulFlags = atoui(lpDBRow[3]);
- sItem.ulObjSize = 0;
- sItem.ulStoreId = 0;
- sItem.ulParentType = (lpDBRow[4])?atoui(lpDBRow[4]) : 0;
- sItem.sEntryId.__size = 0;
- sItem.sEntryId.__ptr = NULL;
- sItem.ulMessageFlags = lpDBRow[5] ? atoui(lpDBRow[5]) : 0;
- sItem.fInOGQueue = lpDBRow[6] ? true : false;
- // Validate deleted object, if not valid, break directly
- er = ValidateDeleteObject(lpSession, bCheckPermission, ulFlags, sItem);
- if (er != erSuccess)
- goto exit;
- // Get extended data
- if(sItem.ulObjType == MAPI_STORE || sItem.ulObjType == MAPI_FOLDER || sItem.ulObjType == MAPI_MESSAGE) {
-
- lpCacheManager->GetStore(sItem.ulId, &sItem.ulStoreId , NULL); //CHECKme:"oude gaf geen errors
- if (!(sItem.ulFlags & MSGFLAG_DELETED))
- GetObjectSize(lpDatabase, sItem.ulId, &sItem.ulObjSize);
- lpCacheManager->GetEntryIdFromObject(sItem.ulId, NULL, 0, &sItem.sEntryId);//CHECKme:"oude gaf geen errors
- GetSourceKey(sItem.ulId, &sItem.sSourceKey);
- GetSourceKey(sItem.ulParent, &sItem.sParentSourceKey);
- }
- lstDeleteItems.push_back(sItem);
- }
- }
- // Now, run through the list, adding children to the bottom of the list. This means
- // we're actually running width-first, and don't have to do anything recursive.
- for (const auto &di : lstDeleteItems) {
- strQuery = "SELECT id, type, flags, (SELECT hierarchy_id FROM outgoingqueue WHERE outgoingqueue.hierarchy_id = hierarchy.id LIMIT 1) FROM hierarchy WHERE parent=" +
- stringify(di.ulId);
- if((ulFlags & EC_DELETE_HARD_DELETE) != EC_DELETE_HARD_DELETE)
- strQuery += " AND (flags&"+stringify(MSGFLAG_DELETED)+") !="+stringify(MSGFLAG_DELETED);
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- goto exit;
- while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL )
- {
- // No id, type or flags exist
- if(lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL)
- continue;
- // Loop protection, don't insert duplicates.
- if (setIDs.insert(atoui(lpDBRow[0])).second == false)
- continue;
- // Add this object as a node to the end of the list
- sItem.fRoot = false;
- sItem.ulObjSize = 0;
- sItem.ulStoreId = 0;
- sItem.sEntryId.__size = 0;
- sItem.sEntryId.__ptr = NULL;
- sItem.ulId = atoui(lpDBRow[0]);
- sItem.ulParent = di.ulId;
- sItem.ulParentType = di.ulObjType;
- sItem.ulObjType = atoi(lpDBRow[1]);
- // Add the parent delete flag, because only the top-level object is marked for deletion
- sItem.ulFlags = atoui(lpDBRow[2]) | (di.ulFlags & MSGFLAG_DELETED);
- sItem.fInOGQueue = lpDBRow[3] ? true : false;
- // Validate deleted object, if no valid, break directly
- er = ValidateDeleteObject(lpSession, bCheckPermission, ulFlags, sItem);
- if (er != erSuccess)
- goto exit;
- if(sItem.ulObjType == MAPI_STORE || sItem.ulObjType == MAPI_FOLDER || (sItem.ulObjType == MAPI_MESSAGE && sItem.ulParentType == MAPI_FOLDER) ) {
-
- lpCacheManager->GetStore(sItem.ulId, &sItem.ulStoreId , NULL); //CHECKme:"oude gaf geen errors
- if (!(sItem.ulFlags & MSGFLAG_DELETED))
- GetObjectSize(lpDatabase, sItem.ulId, &sItem.ulObjSize);
- lpCacheManager->GetEntryIdFromObject(sItem.ulId, NULL, 0, &sItem.sEntryId);//CHECKme:"oude gaf geen errors
- GetSourceKey(sItem.ulId, &sItem.sSourceKey);
- GetSourceKey(sItem.ulParent, &sItem.sParentSourceKey);
- }
-
- lstDeleteItems.push_back(sItem);
- }
- }
-
- // Move list
- std::swap(lstDeleteItems, *lplstDeleteItems);
- exit:
- FreeDeletedItems(&lstDeleteItems);
- return er;
- }
- /*
- * Add changes into the ICS system.
- *
- * This adds a change for each removed folder and message. The change could be a soft- or hard delete.
- * It is possible to gives a list with different deleted objects, all not supported object types will be skipped.
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] lstDeleted List with deleted objects
- * @param[in] ulSyncId ???
- *
- */
- ECRESULT DeleteObjectUpdateICS(ECSession *lpSession, unsigned int ulFlags, ECListDeleteItems &lstDeleted, unsigned int ulSyncId)
- {
- ECRESULT er = erSuccess;
- for (const auto &di : lstDeleted)
- // ICS update
- if (di.ulObjType == MAPI_MESSAGE &&
- di.ulParentType == MAPI_FOLDER)
- AddChange(lpSession, ulSyncId, di.sSourceKey, di.sParentSourceKey, ulFlags & EC_DELETE_HARD_DELETE ? ICS_MESSAGE_HARD_DELETE : ICS_MESSAGE_SOFT_DELETE);
- else if (di.ulObjType == MAPI_FOLDER &&
- !(di.ulFlags & FOLDER_SEARCH))
- AddChange(lpSession, ulSyncId, di.sSourceKey, di.sParentSourceKey, ulFlags & EC_DELETE_HARD_DELETE ? ICS_FOLDER_HARD_DELETE : ICS_FOLDER_SOFT_DELETE);
- return er;
- }
- /**
- * Check if the delete of the object should actually occur in the sync
- * scope. Checks the syncedmessages table.
- *
- * @param lpDatabase Database object
- * @param lstDeleted Expanded list of all objects to check
- * @param ulSyncId syncid to check with
- *
- * @return Kopano error code
- */
- static ECRESULT CheckICSDeleteScope(ECDatabase *lpDatabase,
- ECListDeleteItems &lstDeleted, unsigned int ulSyncId)
- {
- ECRESULT er;
- for (auto iterDeleteItems = lstDeleted.begin();
- iterDeleteItems != lstDeleted.end(); ) {
- er = CheckWithinLastSyncedMessagesSet(lpDatabase, ulSyncId, iterDeleteItems->sSourceKey);
- if (er == KCERR_NOT_FOUND) {
- // ignore delete of message
- ec_log_debug("Message not in sync scope, ignoring delete");
- FreeDeleteItem(&(*iterDeleteItems));
- lstDeleted.erase(iterDeleteItems++);
- } else if (er != erSuccess)
- return er;
- else
- ++iterDeleteItems;
- }
- return erSuccess;
- }
- /*
- * Calculate and update the store size for deleted items
- *
- * The DeleteObjectStoreSize methode calculate and update the store size. Only top-level messages will
- * be calculate, all other objects are not supported and will be skipped. If a message has the
- * MSGFLAG_DELETED flag, the size will ignored because it is already subtract from the store size.
- * The deleted object list may include more than one store.
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] lpDatabase Reference to a database object; cannot be NULL.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] lstDeleted List with deleted objects
- */
- ECRESULT DeleteObjectStoreSize(ECSession *lpSession, ECDatabase *lpDatabase, unsigned int ulFlags, ECListDeleteItems &lstDeleted)
- {
- ECRESULT er = erSuccess;
- std::map<unsigned int, long long> mapStoreSize;
- //TODO: check or foldersize also is used
- for (const auto &di : lstDeleted) {
- // Get size of all the messages
- bool k = di.ulObjType == MAPI_MESSAGE &&
- di.ulParentType == MAPI_FOLDER &&
- (di.ulFlags & MSGFLAG_DELETED) != MSGFLAG_DELETED;
- if (!k)
- continue;
- assert(di.ulStoreId != 0);
- if (mapStoreSize.find(di.ulStoreId) != mapStoreSize.end() )
- mapStoreSize[di.ulStoreId] += di.ulObjSize;
- else
- mapStoreSize[di.ulStoreId] = di.ulObjSize;
- }
- // Update store size for each store
- for (auto i = mapStoreSize.cbegin();
- i != mapStoreSize.cend() && er == erSuccess; ++i)
- if (i->second > 0)
- er = UpdateObjectSize(lpDatabase, i->first, MAPI_STORE, UPDATE_SUB, i->second);
- return er;
- }
- /*
- * Soft delete objects, mark the root objects as deleted
- *
- * Mark the root objects as deleted, add deleted on date on the root objects.
- * Since this is a fairly simple operation, we are doing soft deletes in a single transaction. Once the SQL has gone OK,
- * we know that all items were successfully mark as deleted and we can therefore add all soft-deleted items into
- * the 'lstDeleted' list at once
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] lpDatabase Reference to a database object; cannot be NULL.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] lstDeleteItems List with objecta which must be deleted.
- * @param[out] lstDeleted List with deleted objects.
- *
- */
- ECRESULT DeleteObjectSoft(ECSession *lpSession, ECDatabase *lpDatabase, unsigned int ulFlags, ECListDeleteItems &lstDeleteItems, ECListDeleteItems &lstDeleted)
- {
- ECRESULT er;
- FILETIME ft;
- std::string strInclauseOQQ;
- std::string strInclause;
- std::string strQuery;
-
- PARENTINFO pi;
-
- std::map<unsigned int, PARENTINFO> mapFolderCounts;
- // Build where condition
- for (const auto &di : lstDeleteItems) {
- bool k = (di.ulObjType == MAPI_MESSAGE &&
- di.ulParentType == MAPI_FOLDER) ||
- di.ulObjType == MAPI_FOLDER ||
- di.ulObjType == MAPI_STORE;
- if (!k)
- continue;
- if (di.fInOGQueue) {
- if(!strInclauseOQQ.empty())
- strInclauseOQQ += ",";
- strInclauseOQQ += stringify(di.ulId);
- }
- if (!di.fRoot)
- continue;
- if (!strInclause.empty())
- strInclause += ",";
- strInclause += stringify(di.ulId);
- // Track counter changes
- if (di.ulParentType != MAPI_FOLDER)
- continue;
- // Ignore already-softdeleted items
- if ((di.ulFlags & MSGFLAG_DELETED) != 0)
- continue;
- if (di.ulObjType == MAPI_MESSAGE) {
- if (di.ulFlags & MAPI_ASSOCIATED) {
- --mapFolderCounts[di.ulParent].lAssoc;
- ++mapFolderCounts[di.ulParent].lDeletedAssoc;
- } else {
- --mapFolderCounts[di.ulParent].lItems;
- ++mapFolderCounts[di.ulParent].lDeleted;
- if ((di.ulMessageFlags & MSGFLAG_READ) == 0)
- --mapFolderCounts[di.ulParent].lUnread;
- }
- }
- if (di.ulObjType == MAPI_FOLDER) {
- --mapFolderCounts[di.ulParent].lFolders;
- ++mapFolderCounts[di.ulParent].lDeletedFolders;
- }
- }
- // Mark all items as deleted, if a item in the outgoingqueue and remove the submit flag
- if (!strInclauseOQQ.empty())
- {
- // Remove any entries in the outgoing queue for deleted items
- strQuery = "DELETE FROM outgoingqueue WHERE hierarchy_id IN ( " + strInclauseOQQ + ")";
- er = lpDatabase->DoDelete(strQuery);
- if(er!= erSuccess)
- return er;
-
- // Remove the submit flag
- 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));
- er = lpDatabase->DoUpdate(strQuery);
- if(er!= erSuccess)
- return er;
- }
- if(!strInclause.empty())
- {
- // Mark item as deleted
- strQuery = "UPDATE hierarchy SET flags=flags|"+stringify(MSGFLAG_DELETED)+" WHERE id IN(" + strInclause + ")";
- er = lpDatabase->DoUpdate(strQuery);
- if(er!= erSuccess)
- return er;
- // Remove the MSGSTATUS_DELMARKED flag (IMAP gateway set)
- strQuery = "UPDATE properties SET val_ulong=val_ulong&~" + stringify(MSGSTATUS_DELMARKED) +
- " WHERE hierarchyid IN(" + strInclause + ") AND tag = " + stringify(PROP_ID(PR_MSG_STATUS)) + " and type = " + stringify(PROP_TYPE(PR_MSG_STATUS));
- er = lpDatabase->DoUpdate(strQuery);
- if(er!= erSuccess)
- return er;
- }
- er = ApplyFolderCounts(lpDatabase, mapFolderCounts);
- if(er != erSuccess)
- return er;
- // Add properties: PR_DELETED_ON
- GetSystemTimeAsFileTime(&ft);
- for (const auto &di : lstDeleteItems) {
- bool k = di.fRoot == true &&
- ((di.ulObjType == MAPI_MESSAGE &&
- di.ulParentType == MAPI_FOLDER) ||
- di.ulObjType == MAPI_FOLDER ||
- di.ulObjType == MAPI_STORE);
- if (!k)
- continue;
- strQuery = "INSERT INTO properties(hierarchyid, tag, type, val_lo, val_hi) VALUES(" +
- stringify(di.ulId) + "," +
- 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);
- er = lpDatabase->DoUpdate(strQuery);
- if (er!= erSuccess)
- return er;
- er = ECTPropsPurge::AddDeferredUpdateNoPurge(lpDatabase,
- di.ulParent, 0, di.ulId);
- if (er != erSuccess)
- return er;
- }
- lstDeleted = lstDeleteItems;
- return erSuccess;
- }
- /**
- * Hard delete objects, remove the data from storage
- *
- * This means we should be really deleting the actual data from the database and storage. This will be done in
- * bachtches of 32 items each because deleteing records is generally fairly slow. Also, very large delete batches
- * can taking up to more than an hour to process. We don't want to have a transaction lasting an hour because it
- * would cause lots of locking problems. Also, each item successfully deleted and committed to the database will
- * added into a list. So, If something fails we notify the items in the 'deleted items list' only.
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] lpDatabase Reference to a database object; cannot be NULL.
- * @param[in] lpAttachmentStorage Reference to an Attachment object. could not NULL if bNoTransaction is true.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] lstDeleteItems List with objects which must be deleted.
- * @param[in] bNoTransaction Disable the database transactions.
- * @param[in] lstDeleted List with deleted objects.
- *
- * @return Kopano error code
- */
- ECRESULT DeleteObjectHard(ECSession *lpSession, ECDatabase *lpDatabase, ECAttachmentStorage *lpAttachmentStorage, unsigned int ulFlags, ECListDeleteItems &lstDeleteItems, bool bNoTransaction, ECListDeleteItems &lstDeleted)
- {
- ECRESULT er = erSuccess;
- object_ptr<ECAttachmentStorage> lpInternalAttachmentStorage;
- std::list<ULONG> lstDeleteAttachments;
- std::string strInclause;
- std::string strOGQInclause;
- std::string strQuery;
- ECListDeleteItems lstToBeDeleted;
- PARENTINFO pi;
-
- std::map<unsigned int, PARENTINFO> mapFolderCounts;
- int i;
- if(!(ulFlags & EC_DELETE_HARD_DELETE)) {
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- if (bNoTransaction && lpAttachmentStorage == NULL) {
- assert(false);
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- if (!lpAttachmentStorage) {
- er = CreateAttachmentStorage(lpDatabase, &~lpInternalAttachmentStorage);
- if (er != erSuccess)
- goto exit;
- lpAttachmentStorage = lpInternalAttachmentStorage;
- }
- for (auto iterDeleteItems = lstDeleteItems.crbegin();
- iterDeleteItems != lstDeleteItems.crend(); ) {
- strInclause.clear();
- strOGQInclause.clear();
- lstDeleteAttachments.clear();
- lstToBeDeleted.clear();
- i = 0;
- // Delete max 32 items per query
- while (i < 32 && iterDeleteItems != lstDeleteItems.crend()) {
- if(!strInclause.empty())
- strInclause += ",";
- strInclause += stringify(iterDeleteItems->ulId);
- if(iterDeleteItems->fInOGQueue) {
- if(!strOGQInclause.empty())
- strOGQInclause += ",";
-
- strOGQInclause += stringify(iterDeleteItems->ulId);
- }
- // make new list for attachment deletes. messages can have imap "attachment".
- if (iterDeleteItems->ulObjType == MAPI_ATTACH || (iterDeleteItems->ulObjType == MAPI_MESSAGE && iterDeleteItems->ulParentType == MAPI_FOLDER))
- lstDeleteAttachments.push_back(iterDeleteItems->ulId);
- lstToBeDeleted.push_front(*iterDeleteItems);
- if(!(ulFlags&EC_DELETE_STORE) && iterDeleteItems->ulParentType == MAPI_FOLDER && iterDeleteItems->fRoot) {
- // Track counter changes
- memset(&pi, 0, sizeof(pi));
- pi.ulStoreId = iterDeleteItems->ulStoreId;
- mapFolderCounts.insert(std::make_pair(iterDeleteItems->ulParent, pi));
- if(iterDeleteItems->ulObjType == MAPI_MESSAGE) {
- if(iterDeleteItems->ulFlags == MAPI_ASSOCIATED) {
- // Delete associated
- --mapFolderCounts[iterDeleteItems->ulParent].lAssoc;
- } else if(iterDeleteItems->ulFlags == 0) {
- // Deleting directly from normal item, count normal and unread items
- --mapFolderCounts[iterDeleteItems->ulParent].lItems;
- if((iterDeleteItems->ulMessageFlags & MSGFLAG_READ) == 0)
- --mapFolderCounts[iterDeleteItems->ulParent].lUnread;
- } else if(iterDeleteItems->ulFlags == (MAPI_ASSOCIATED | MSGFLAG_DELETED)) {
- // Deleting softdeleted associated item
- --mapFolderCounts[iterDeleteItems->ulParent].lDeletedAssoc;
- } else {
- // Deleting normal softdeleted item
- --mapFolderCounts[iterDeleteItems->ulParent].lDeleted;
- }
- }
- if(iterDeleteItems->ulObjType == MAPI_FOLDER) {
- if ((iterDeleteItems->ulFlags & MSGFLAG_DELETED) == 0)
- --mapFolderCounts[iterDeleteItems->ulParent].lFolders;
- else
- --mapFolderCounts[iterDeleteItems->ulParent].lDeletedFolders;
- }
- }
- ++i;
- ++iterDeleteItems;
- }
- // Start transaction
- if (!bNoTransaction) {
- er = lpAttachmentStorage->Begin();
- if (er != erSuccess)
- goto exit;
- er = lpDatabase->Begin();
- if (er != erSuccess)
- goto exit;
- }
- if(!strInclause.empty()) {
- // First, Remove any entries in the outgoing queue for deleted items
- if(!strOGQInclause.empty()) {
- strQuery = "DELETE FROM outgoingqueue WHERE hierarchy_id IN ( " + strOGQInclause + ")";
- er = lpDatabase->DoDelete(strQuery);
- if(er!= erSuccess)
- goto exit;
- }
-
- // Then, the hierarchy entries of all the objects
- strQuery = "DELETE FROM hierarchy WHERE id IN (" + strInclause + ")";
- er = lpDatabase->DoDelete(strQuery);
- if(er!= erSuccess)
- goto exit;
- // Then, the table properties for the objects we just deleted
- strQuery = "DELETE FROM tproperties WHERE hierarchyid IN (" + strInclause + ")";
- er = lpDatabase->DoDelete(strQuery);
- if(er!= erSuccess)
- goto exit;
- // Then, the properties for the objects we just deleted
- strQuery = "DELETE FROM properties WHERE hierarchyid IN (" + strInclause + ")";
- er = lpDatabase->DoDelete(strQuery);
- if(er!= erSuccess)
- goto exit;
- // Then, the MVproperties for the objects we just deleted
- strQuery = "DELETE FROM mvproperties WHERE hierarchyid IN (" + strInclause + ")";
- er = lpDatabase->DoDelete(strQuery);
- if(er!= erSuccess)
- goto exit;
- // Then, the acls for the objects we just deleted (if exist)
- strQuery = "DELETE FROM acl WHERE hierarchy_id IN (" + strInclause + ")";
- er = lpDatabase->DoDelete(strQuery);
- if(er != erSuccess)
- goto exit;
- // remove indexedproperties
- strQuery = "DELETE FROM indexedproperties WHERE hierarchyid IN (" + strInclause + ")";
- er = lpDatabase->DoDelete(strQuery);
- if(er != erSuccess)
- goto exit;
- // remove deferred table updates
- strQuery = "DELETE FROM deferredupdate WHERE hierarchyid IN (" + strInclause + ")";
- er = lpDatabase->DoDelete(strQuery);
- if(er != erSuccess)
- goto exit;
-
- }
- // list may contain non-attachment object IDs!
- if (!lstDeleteAttachments.empty()) {
- er = lpAttachmentStorage->DeleteAttachments(lstDeleteAttachments);
- if (er != erSuccess)
- goto exit;
- }
- er = ApplyFolderCounts(lpDatabase, mapFolderCounts);
- if(er != erSuccess)
- goto exit;
- // Clear map for next round
- mapFolderCounts.clear();
- // Commit the transaction
- if (!bNoTransaction) {
- er = lpAttachmentStorage->Commit();
- if (er != erSuccess)
- goto exit;
- er = lpDatabase->Commit();
- if(er != erSuccess)
- goto exit;
- }
- // Deletes have been committed, add the deleted items to the list of items we have deleted
- lstDeleted.splice(lstDeleted.begin(),lstToBeDeleted);
- } // while iterDeleteItems != end()
- exit:
- if (er != erSuccess && !bNoTransaction) {
- if(lpInternalAttachmentStorage)
- lpInternalAttachmentStorage->Rollback();
- lpDatabase->Rollback();
- }
- return er;
- }
- /*
- * Deleted object cache updates
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] lstDeleted List with deleted objects.
- */
- ECRESULT DeleteObjectCacheUpdate(ECSession *lpSession, unsigned int ulFlags, ECListDeleteItems &lstDeleted)
- {
- ECSessionManager *lpSessionManager = NULL;
- ECCacheManager *lpCacheManager = NULL;
- if (lpSession == NULL)
- return KCERR_INVALID_PARAMETER;
- lpSessionManager = lpSession->GetSessionManager();
- lpCacheManager = lpSessionManager->GetCacheManager();
- // Remove items from cache and update the outgoing queue
- for (const auto &di : lstDeleted) {
- // update the cache
- lpCacheManager->Update(fnevObjectDeleted, di.ulId);
- if (di.fRoot)
- lpCacheManager->Update(fnevObjectModified, di.ulParent);
- // Update cache, Remove index properties
- if (ulFlags & EC_DELETE_HARD_DELETE)
- lpCacheManager->RemoveIndexData(di.ulId);
- }
- return erSuccess;
- }
- /*
- * Deleted object notifications
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] lstDeleted List with deleted objects.
- */
- ECRESULT DeleteObjectNotifications(ECSession *lpSession, unsigned int ulFlags, ECListDeleteItems &lstDeleted)
- {
- ECSessionManager *lpSessionManager = NULL;
- std::list<unsigned int> lstParent;
- ECMapTableChangeNotifications mapTableChangeNotifications;
- //std::set<unsigned int> setFolderParents;
- size_t cDeleteditems = lstDeleted.size();
- unsigned int ulGrandParent = 0;
- if (lpSession == NULL)
- return KCERR_INVALID_PARAMETER;
- lpSessionManager = lpSession->GetSessionManager();
- // Now, send the notifications for MAPI_MESSAGE and MAPI_FOLDER types
- for (auto &di : lstDeleted) {
- // Update the outgoing queue
- // Remove the item from both the local and master outgoing queues
- if ((di.ulFlags & MSGFLAG_SUBMIT) &&
- di.ulParentType == MAPI_FOLDER &&
- di.ulObjType == MAPI_MESSAGE) {
- lpSessionManager->UpdateOutgoingTables(ECKeyTable::TABLE_ROW_DELETE, di.ulStoreId, di.ulId, EC_SUBMIT_LOCAL, MAPI_MESSAGE);
- lpSessionManager->UpdateOutgoingTables(ECKeyTable::TABLE_ROW_DELETE, di.ulStoreId, di.ulId, EC_SUBMIT_MASTER, MAPI_MESSAGE);
- }
- bool k = (di.ulParentType == MAPI_FOLDER &&
- di.ulObjType == MAPI_MESSAGE) ||
- di.ulObjType == MAPI_FOLDER;
- if (!k)
- continue;
- // Notify that the message has been deleted
- lpSessionManager->NotificationDeleted(di.ulObjType, di.ulId,
- di.ulStoreId, &di.sEntryId, di.ulParent,
- di.ulFlags & (MSGFLAG_ASSOCIATED | MSGFLAG_DELETED));
- // Update all tables viewing this message
- if (cDeleteditems < EC_TABLE_CHANGE_THRESHOLD) {
- lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_DELETE,
- di.ulFlags & (MSGFLAG_ASSOCIATED | MSGFLAG_DELETED),
- di.ulParent, di.ulId, di.ulObjType);
- if ((ulFlags & EC_DELETE_HARD_DELETE) != EC_DELETE_HARD_DELETE)
- // Update all tables viewing this message
- lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_ADD,
- MSGFLAG_DELETED, di.ulParent, di.ulId, di.ulObjType);
- } else {
- // We need to send a table change notifications later on
- mapTableChangeNotifications[di.ulParent].insert(TABLECHANGENOTIFICATION(di.ulObjType, di.ulFlags & MSGFLAG_NOTIFY_FLAGS));
- if ((ulFlags & EC_DELETE_HARD_DELETE) != EC_DELETE_HARD_DELETE)
- mapTableChangeNotifications[di.ulParent].insert(TABLECHANGENOTIFICATION(di.ulObjType, (di.ulFlags & MSGFLAG_NOTIFY_FLAGS) | MSGFLAG_DELETED));
- }
- // @todo: Is this correct ???
- if (di.fRoot)
- lstParent.push_back(di.ulParent);
- }
- // We have a list of all the folders in which something was deleted, so get a unique list
- lstParent.sort();
- lstParent.unique();
- // Now, send each parent folder a notification that it has been altered and send
- // its parent a notification (ie the grandparent of the deleted object) that its
- // hierarchy table has been changed.
- for (auto pa_id : lstParent) {
- if(cDeleteditems >= EC_TABLE_CHANGE_THRESHOLD) {
- // Find the set of notifications to send for the current parent.
- auto pn = mapTableChangeNotifications.find(pa_id);
- if (pn != mapTableChangeNotifications.cend())
- // Iterate the set and send notifications.
- for (auto n = pn->second.cbegin();
- n != pn->second.cend(); ++n)
- lpSessionManager->UpdateTables(ECKeyTable::TABLE_CHANGE,
- n->ulFlags, pa_id, 0, n->ulType);
- }
- lpSessionManager->NotificationModified(MAPI_FOLDER, pa_id);
- if (lpSessionManager->GetCacheManager()->GetParent(pa_id, &ulGrandParent) == erSuccess)
- lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY,
- 0, ulGrandParent, pa_id, MAPI_FOLDER);
- }
- return erSuccess;
- }
- /**
- * Mark a store as deleted
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] lpDatabase Reference to a database object; cannot be NULL.
- * @param[in] ulStoreHierarchyId Store id to be delete
- * @param[in] ulSyncId ??????
- *
- * @return Kopano error code
- */
- ECRESULT MarkStoreAsDeleted(ECSession *lpSession, ECDatabase *lpDatabase, unsigned int ulStoreHierarchyId, unsigned int ulSyncId)
- {
- ECRESULT er;
- std::string strQuery;
- ECSessionManager *lpSessionManager = NULL;
- ECSearchFolders *lpSearchFolders = NULL;
- ECCacheManager *lpCacheManager = NULL;
- FILETIME ft;
- if (lpSession == NULL || lpDatabase == NULL)
- return KCERR_INVALID_PARAMETER;
- lpSessionManager = lpSession->GetSessionManager();
- lpSearchFolders = lpSessionManager->GetSearchFolders();
- lpCacheManager = lpSessionManager->GetCacheManager();
- // Remove search results for deleted store
- lpSearchFolders->RemoveSearchFolder(ulStoreHierarchyId);
- // Mark item as deleted
- strQuery = "UPDATE hierarchy SET flags=flags|"+stringify(MSGFLAG_DELETED)+" WHERE id="+stringify(ulStoreHierarchyId);
- er = lpDatabase->DoUpdate(strQuery);
- if(er!= erSuccess)
- return er;
- // Add properties: PR_DELETED_ON
- GetSystemTimeAsFileTime(&ft);
- 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);
- er = lpDatabase->DoUpdate(strQuery);
- if(er!= erSuccess)
- return er;
- lpCacheManager->Update(fnevObjectDeleted, ulStoreHierarchyId);
- return erSuccess;
- }
- /*
- * Delete objects from different stores.
- *
- * Delete a store, folders, messages, reciepints and attachments.
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] lpDatabase Reference to a database object; cannot be NULL.
- * @param[in] ulObjectId Itemid which must be expand.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] ulSyncId ??????
- * @param[in] bNoTransaction Disable the database transactions.
- * @param[in] bCheckPermission Check the objects delete permissions.
- *
- * @return Kopano error code
- */
- ECRESULT DeleteObjects(ECSession *lpSession, ECDatabase *lpDatabase, unsigned int ulObjectId, unsigned int ulFlags, unsigned int ulSyncId, bool bNoTransaction, bool bCheckPermission)
- {
- ECListInt sObjectList = {ulObjectId};
- return DeleteObjects(lpSession, lpDatabase, &sObjectList, ulFlags, ulSyncId, bNoTransaction, bCheckPermission);
- }
- /*
- * Delete objects from different stores.
- *
- * Delete a store, folders, messages, reciepints and attachments.
- *
- * @param[in] lpSession Reference to a session object; cannot be NULL.
- * @param[in] lpDatabase Reference to a database object; cannot be NULL.
- * @param[in] lpsObjectList Reference to a list of objects that contains itemid and must be expanded.
- * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
- * @param[in] ulSyncId ??????
- * @param[in] bNoTransaction Disable the database transactions.
- * @param[in] bCheckPermission Check the objects delete permissions.
- *
- * @return Kopano error code
- */
- ECRESULT DeleteObjects(ECSession *lpSession, ECDatabase *lpDatabase, ECListInt *lpsObjectList, unsigned int ulFlags, unsigned int ulSyncId, bool bNoTransaction, bool bCheckPermission)
- {
- ECRESULT er = erSuccess;
- ECListDeleteItems lstDeleteItems;
- ECListDeleteItems lstDeleted;
- ECSearchFolders *lpSearchFolders = NULL;
- ECSessionManager *lpSessionManager = NULL;
-
- if (lpSession == NULL || lpDatabase == NULL || lpsObjectList == NULL) {
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- // Make sure we're only deleting things once
- lpsObjectList->sort();
- lpsObjectList->unique();
- lpSessionManager = lpSession->GetSessionManager();
- lpSearchFolders = lpSessionManager->GetSearchFolders();
- if ((bNoTransaction && (ulFlags & EC_DELETE_HARD_DELETE)) ||
- (bNoTransaction && (ulFlags&EC_DELETE_STORE)) ) {
- assert(false); // This means that the caller has a transaction but that's not allowed
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- if(!(ulFlags & EC_DELETE_HARD_DELETE) && !bNoTransaction) {
- er = lpDatabase->Begin();
- if (er != erSuccess)
- goto exit;
- }
- // Collect recursive parent objects, validate item and check the permissions
- er = ExpandDeletedItems(lpSession, lpDatabase, lpsObjectList, ulFlags, bCheckPermission, &lstDeleteItems);
- if (er != erSuccess) {
- ec_log_info("Error while expanding delete item list, error code %u", er);
- goto exit;
- }
- // Remove search results for deleted folders
- if (ulFlags & EC_DELETE_STORE)
- lpSearchFolders->RemoveSearchFolder(*lpsObjectList->begin());
- else
- for (const auto &di : lstDeleteItems)
- if (di.ulObjType == MAPI_FOLDER &&
- di.ulFlags == FOLDER_SEARCH)
- lpSearchFolders->RemoveSearchFolder(di.ulStoreId, di.ulId);
- // before actual delete check items which are outside the sync scope
- if (ulSyncId != 0)
- CheckICSDeleteScope(lpDatabase, lstDeleteItems, ulSyncId);
-
- // Mark or delete objects
- if(ulFlags & EC_DELETE_HARD_DELETE)
- er = DeleteObjectHard(lpSession, lpDatabase, NULL, ulFlags, lstDeleteItems, bNoTransaction, lstDeleted);
- else
- er = DeleteObjectSoft(lpSession, lpDatabase, ulFlags, lstDeleteItems, lstDeleted);
- if (er != erSuccess) {
- ec_log_info("Error while deleting expanded item list, error code %u", er);
- goto exit;
- }
- if (!(ulFlags&EC_DELETE_STORE)) {
- //Those functions are not called with a store delete
- // Update store size
- er = DeleteObjectStoreSize(lpSession, lpDatabase, ulFlags, lstDeleted);
- if(er!= erSuccess) {
- ec_log_info("Error while updating store sizes after delete, error code %u", er);
- goto exit;
- }
- // Update ICS
- er = DeleteObjectUpdateICS(lpSession, ulFlags, lstDeleted, ulSyncId);
- if (er != erSuccess) {
- ec_log_info("Error while updating ICS after delete, error code %u", er);
- goto exit;
- }
- // Update local commit time on top level folders
- for (const auto &di : lstDeleteItems) {
- bool k = !(ulFlags & EC_DELETE_HARD_DELETE) &&
- di.fRoot && di.ulParentType == MAPI_FOLDER &&
- di.ulObjType == MAPI_MESSAGE;
- if (!k)
- continue;
- // directly hard-delete the item is not supported for updating PR_LOCAL_COMMIT_TIME_MAX
- er = WriteLocalCommitTimeMax(NULL, lpDatabase, di.ulParent, NULL);
- if (er != erSuccess) {
- ec_log_info("Error while updating folder access time after delete, error code %u", er);
- goto exit;
- }
- // the folder will receive a changed notification anyway, since items are being deleted from it
- }
- }
-
- // Finish transaction
- if(!(ulFlags & EC_DELETE_HARD_DELETE) && !bNoTransaction) {
- er = lpDatabase->Commit();
- if (er != erSuccess)
- goto exit;
- }
- // Update cache
- DeleteObjectCacheUpdate(lpSession, ulFlags, lstDeleted);
- // Send notifications
- if (!(ulFlags&EC_DELETE_STORE))
- DeleteObjectNotifications(lpSession, ulFlags, lstDeleted);
- exit:
- if (er != erSuccess && lpDatabase != NULL && !bNoTransaction &&
- !(ulFlags & EC_DELETE_HARD_DELETE))
- lpDatabase->Rollback();
- FreeDeletedItems(&lstDeleteItems);
- return er;
- }
- /**
- * Update PR_LOCAL_COMMIT_TIME_MAX property on a folder which contents has changed.
- *
- * This function should be called when the contents of a folder change
- * Affected: saveObject, emptyFolder, deleteObjects, (not done: copyObjects, copyFolder)
- *
- * @param[in] soap soap struct used for allocating memory for return value, can be NULL
- * @param[in] lpDatabase database pointer, should be in transaction already
- * @param[in] ulFolderId folder to update property in
- * @param[out] ppvTime time property that was written on the folder, can be NULL
- *
- * @return Kopano error code
- * @retval KCERR_DATABASE_ERROR database could not be updated
- */
- // @todo add parameter to pass ulFolderIdType, to check that it contains MAPI_FOLDER.
- ECRESULT WriteLocalCommitTimeMax(struct soap *soap, ECDatabase *lpDatabase, unsigned int ulFolderId, propVal **ppvTime)
- {
- ECRESULT er;
- FILETIME ftNow;
- std::string strQuery;
- propVal *pvTime = NULL;
- GetSystemTimeAsFileTime(&ftNow);
- if (soap && ppvTime) {
- pvTime = s_alloc<propVal>(soap);
- pvTime->ulPropTag = PR_LOCAL_COMMIT_TIME_MAX;
- pvTime->__union = SOAP_UNION_propValData_hilo;
- pvTime->Value.hilo = s_alloc<hiloLong>(soap);
- pvTime->Value.hilo->hi = ftNow.dwHighDateTime;
- pvTime->Value.hilo->lo = ftNow.dwLowDateTime;
- }
- strQuery = "INSERT INTO properties (hierarchyid, tag, type, val_hi, val_lo) VALUES ("
- +stringify(ulFolderId)+","+stringify(PROP_ID(PR_LOCAL_COMMIT_TIME_MAX))+","+stringify(PROP_TYPE(PR_LOCAL_COMMIT_TIME_MAX))+","
- +stringify(ftNow.dwHighDateTime)+","+stringify(ftNow.dwLowDateTime)+")"+
- " ON DUPLICATE KEY UPDATE val_hi="+stringify(ftNow.dwHighDateTime)+",val_lo="+stringify(ftNow.dwLowDateTime);
- er = lpDatabase->DoInsert(strQuery);
- if (er != erSuccess)
- return er;
- if (ppvTime)
- *ppvTime = std::move(pvTime);
- return erSuccess;
- }
- void FreeDeleteItem(DELETEITEM *src)
- {
- s_free(nullptr, src->sEntryId.__ptr);
- }
- void FreeDeletedItems(ECListDeleteItems *lplstDeleteItems)
- {
- for (auto &di : *lplstDeleteItems)
- FreeDeleteItem(&di);
- lplstDeleteItems->clear();
- }
- /**
- * Update value in tproperties by taking value from properties for a list of objects
- *
- * This should be called whenever a value is changed in the 'properties' table outside WriteProps(). It updates the value
- * in tproperties if necessary (it may not be in tproperties at all).
- *
- * @param[in] lpDatabase Database handle
- * @param[in] ulPropTag Property tag to update in tproperties
- * @param[in] ulFolderId Folder ID for all objects in lpObjectIDs
- * @param[in] lpObjectIDs List of object IDs to update
- * @return result
- */
- ECRESULT UpdateTProp(ECDatabase *lpDatabase, unsigned int ulPropTag, unsigned int ulFolderId, ECListInt *lpObjectIDs) {
- std::string strQuery;
- if(lpObjectIDs->empty())
- return erSuccess; // Nothing to do
- // Update tproperties by taking value from properties
- 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 "
- "WHERE properties.tag = " + stringify(PROP_ID(ulPropTag)) + " AND properties.type = " + stringify(PROP_TYPE(ulPropTag)) + " AND tproperties.folderid = " + stringify(ulFolderId) + " AND properties.hierarchyid IN (";
-
- for (auto iObjectid = lpObjectIDs->cbegin(); iObjectid != lpObjectIDs->cend(); ++iObjectid) {
- if(iObjectid != lpObjectIDs->cbegin())
- strQuery += ",";
- strQuery += stringify(*iObjectid);
- }
- strQuery += ")";
- return lpDatabase->DoUpdate(strQuery);
- }
- /**
- * Update value in tproperties by taking value from properties for a single object
- *
- * This should be called whenever a value is changed in the 'properties' table outside WriteProps(). It updates the value
- * in tproperties if necessary (it may not be in tproperties at all).
- *
- * @param[in] lpDatabase Database handle
- * @param[in] ulPropTag Property tag to update in tproperties
- * @param[in] ulFolderId Folder ID for all objects in lpObjectIDs
- * @param[in] ulObjId Object ID to update
- * @return result
- */
- ECRESULT UpdateTProp(ECDatabase *lpDatabase, unsigned int ulPropTag, unsigned int ulFolderId, unsigned int ulObjId) {
- ECListInt list;
-
- list.push_back(ulObjId);
-
- return UpdateTProp(lpDatabase, ulPropTag, ulFolderId, &list);
- }
- /**
- * Update folder count for a folder by adding or removing a certain amount
- *
- * This function can be used to incrementally update a folder count of a folder. The lDelta can be positive (counter increases)
- * or negative (counter decreases)
- *
- * @param lpDatabase Database handle
- * @param ulFolderId Folder ID to update
- * @param ulPropTag Counter property to update (must be type that uses val_ulong (PT_LONG or PT_BOOLEAN))
- * @param lDelta Signed integer change
- * @return result
- */
- ECRESULT UpdateFolderCount(ECDatabase *lpDatabase, unsigned int ulFolderId, unsigned int ulPropTag, int lDelta)
- {
- ECRESULT er;
- std::string strQuery;
- unsigned int ulParentId;
- unsigned int ulType;
-
- if(lDelta == 0)
- return erSuccess; // No change
- er = g_lpSessionManager->GetCacheManager()->GetObject(ulFolderId, &ulParentId, NULL, NULL, &ulType);
- if(er != erSuccess)
- return er;
- if (ulType != MAPI_FOLDER) {
- ec_log_info("Not updating folder count %d for non-folder object %d type %d", lDelta, ulFolderId, ulType);
- assert(ulType == MAPI_FOLDER);
- return erSuccess;
- }
- strQuery = "UPDATE properties SET val_ulong = ";
- // make sure val_ulong stays a positive number
- if (lDelta < 0)
- strQuery += "IF (val_ulong >= " + stringify(abs(lDelta),false,true) + ", val_ulong + " + stringify(lDelta,false,true) + ", 0)";
- else
- strQuery += "val_ulong + " + stringify(lDelta,false,true);
- strQuery += " WHERE hierarchyid = " + stringify(ulFolderId) + " AND tag = " + stringify(PROP_ID(ulPropTag)) + " AND type = " + stringify(PROP_TYPE(ulPropTag));
- er = lpDatabase->DoUpdate(strQuery);
- if(er != erSuccess)
- return er;
- er = UpdateTProp(lpDatabase, ulPropTag, ulParentId, ulFolderId);
- if(er != erSuccess)
- return er;
- return erSuccess;
- }
- ECRESULT CheckQuota(ECSession *lpecSession, ULONG ulStoreId)
- {
- ECRESULT er;
- long long llStoreSize = 0;
- eQuotaStatus QuotaStatus = QUOTA_OK;
-
- er = lpecSession->GetSecurity()->GetStoreSize(ulStoreId, &llStoreSize);
- if (er != erSuccess)
- return er;
- er = lpecSession->GetSecurity()->CheckQuota(ulStoreId, llStoreSize, &QuotaStatus);
- if (er != erSuccess)
- return er;
- if (QuotaStatus == QUOTA_HARDLIMIT)
- return KCERR_STORE_FULL;
- return erSuccess;
- }
- ECRESULT MapEntryIdToObjectId(ECSession *lpecSession, ECDatabase *lpDatabase, ULONG ulObjId, const entryId &sEntryId)
- {
- ECRESULT er;
- std::string strQuery;
- er = RemoveStaleIndexedProp(lpDatabase, PR_ENTRYID, sEntryId.__ptr, sEntryId.__size);
- if(er != erSuccess) {
- 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());
- return KCERR_DATABASE_ERROR;
- }
- strQuery = "INSERT INTO indexedproperties (hierarchyid,tag,val_binary) VALUES("+stringify(ulObjId)+", 0x0FFF, "+lpDatabase->EscapeBinary(sEntryId.__ptr, sEntryId.__size)+")";
- er = lpDatabase->DoInsert(strQuery);
- if(er != erSuccess)
- return er;
- g_lpSessionManager->GetCacheManager()->SetObjectEntryId((entryId*)&sEntryId, ulObjId);
- return erSuccess;
- }
- ECRESULT UpdateFolderCounts(ECDatabase *lpDatabase, ULONG ulParentId, ULONG ulFlags, propValArray *lpModProps)
- {
- ECRESULT er = erSuccess;
-
- if (ulFlags & MAPI_ASSOCIATED)
- er = UpdateFolderCount(lpDatabase, ulParentId, PR_ASSOC_CONTENT_COUNT, 1);
- else {
- er = UpdateFolderCount(lpDatabase, ulParentId, PR_CONTENT_COUNT, 1);
- struct propVal *lpPropMessageFlags = NULL;
- lpPropMessageFlags = FindProp(lpModProps, PR_MESSAGE_FLAGS);
- if (er == erSuccess && (!lpPropMessageFlags || (lpPropMessageFlags->Value.ul & MSGFLAG_READ) == 0))
- er = UpdateFolderCount(lpDatabase, ulParentId, PR_CONTENT_UNREAD, 1);
- }
- return er;
- }
- /**
- * Handles the outgoingqueue table according to the PR_MESSAGE_FLAGS
- * of a message. This function does not do transactions, so you must
- * already be in a database transaction.
- *
- * @param[in] lpDatabase Database object
- * @param[in] ulSyncId syncid of the message
- * @param[in] ulStoreId storeid of the message
- * @param[in] ulObjId hierarchyid of the message
- * @param[in] bNewItem message is new
- * @param[in] lpModProps properties of the message
- *
- * @return Kopano error code
- */
- ECRESULT ProcessSubmitFlag(ECDatabase *lpDatabase, ULONG ulSyncId, ULONG ulStoreId, ULONG ulObjId, bool bNewItem, propValArray *lpModProps)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- std::string strQuery;
- struct propVal *lpPropMessageFlags = NULL; // non-free
- ULONG ulPrevSubmitFlag = 0;
-
- // If the messages was saved by an ICS syncer, then we need to sync the PR_MESSAGE_FLAGS for MSGFLAG_SUBMIT if it
- // was included in the save.
- lpPropMessageFlags = FindProp(lpModProps, PR_MESSAGE_FLAGS);
- if (ulSyncId > 0 && lpPropMessageFlags) {
- if (bNewItem) {
- // Item is new, so it's not in the queue at the moment
- ulPrevSubmitFlag = 0;
- } else {
- // Existing item. Check its current submit flag by looking at the outgoing queue.
- strQuery = "SELECT hierarchy_id FROM outgoingqueue WHERE hierarchy_id=" + stringify(ulObjId) + " AND flags & " + stringify(EC_SUBMIT_MASTER) + " = 0 LIMIT 1";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if (er != erSuccess)
- return er;
- // Item is (1)/is not (0) in the outgoing queue at the moment
- ulPrevSubmitFlag = lpDatabase->GetNumRows(lpDBResult) > 0;
- }
- if ((lpPropMessageFlags->Value.ul & MSGFLAG_SUBMIT) && ulPrevSubmitFlag == 0) {
- // Message was previously not submitted, but it is now, so add it to the outgoing queue and set the correct flags.
- strQuery = "INSERT INTO outgoingqueue (store_id, hierarchy_id, flags) VALUES("+stringify(ulStoreId)+", "+stringify(ulObjId)+"," + stringify(EC_SUBMIT_LOCAL) + ")";
- er = lpDatabase->DoInsert(strQuery);
- if (er != erSuccess)
- return er;
- strQuery = "UPDATE properties SET val_ulong = val_ulong | " + stringify(MSGFLAG_SUBMIT) +
- " WHERE hierarchyid = " + stringify(ulObjId) +
- " AND type = " + stringify(PROP_TYPE(PR_MESSAGE_FLAGS)) +
- " AND tag = " + stringify(PROP_ID(PR_MESSAGE_FLAGS));
- er = lpDatabase->DoUpdate(strQuery);
- if (er != erSuccess)
- return er;
- // The object has changed, update the cache.
- g_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulObjId);
- // Update in-memory outgoing tables
- g_lpSessionManager->UpdateOutgoingTables(ECKeyTable::TABLE_ROW_ADD, ulStoreId, ulObjId, EC_SUBMIT_LOCAL, MAPI_MESSAGE);
- } else if ((lpPropMessageFlags->Value.ul & MSGFLAG_SUBMIT) == 0 && ulPrevSubmitFlag == 1) {
- // Message was previously submitted, but is not submitted any more. Remove it from the outgoing queue and remove the flags.
- strQuery = "DELETE FROM outgoingqueue WHERE hierarchy_id = " + stringify(ulObjId);
- er = lpDatabase->DoDelete(strQuery);
- if (er != erSuccess)
- return er;
- strQuery = "UPDATE properties SET val_ulong = val_ulong & ~" + stringify(MSGFLAG_SUBMIT) +
- " WHERE hierarchyid = " + stringify(ulObjId) +
- " AND type = " + stringify(PROP_TYPE(PR_MESSAGE_FLAGS)) +
- " AND tag = " + stringify(PROP_ID(PR_MESSAGE_FLAGS));
- er = lpDatabase->DoUpdate(strQuery);
- if (er != erSuccess)
- return er;
- // The object has changed, update the cache.
- g_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulObjId);
- // Update in-memory outgoing tables
- g_lpSessionManager->UpdateOutgoingTables(ECKeyTable::TABLE_ROW_DELETE, ulStoreId, ulObjId, EC_SUBMIT_LOCAL, MAPI_MESSAGE);
- }
- }
- return erSuccess;
- }
- ECRESULT CreateNotifications(ULONG ulObjId, ULONG ulObjType, ULONG ulParentId, ULONG ulGrandParentId, bool bNewItem, propValArray *lpModProps, struct propVal *lpvCommitTime)
- {
- unsigned int ulObjFlags = 0;
- unsigned int ulParentFlags = 0;
-
- if(!((ulObjType == MAPI_ATTACH || ulObjType == MAPI_MESSAGE) && ulObjType == MAPI_STORE) &&
- (ulObjType == MAPI_MESSAGE || ulObjType == MAPI_FOLDER || ulObjType == MAPI_STORE))
- {
- g_lpSessionManager->GetCacheManager()->GetObjectFlags(ulObjId, &ulObjFlags);
- // update PR_LOCAL_COMMIT_TIME_MAX in cache for disconnected clients who want to know if the folder contents changed
- if (lpvCommitTime) {
- sObjectTableKey key(ulParentId, 0);
- g_lpSessionManager->GetCacheManager()->SetCell(&key, PR_LOCAL_COMMIT_TIME_MAX, lpvCommitTime);
- }
- if (bNewItem) {
- // Notify that the message has been created
- g_lpSessionManager->NotificationCreated(ulObjType, ulObjId, ulParentId);
- g_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_ADD, ulObjFlags & MSGFLAG_NOTIFY_FLAGS, ulParentId, ulObjId, ulObjType);
- // Notify that the folder in which the message resided has changed (PR_CONTENT_COUNT, PR_CONTENT_UNREAD)
-
- if(ulObjFlags & MAPI_ASSOCIATED)
- g_lpSessionManager->GetCacheManager()->UpdateCell(ulParentId, PR_ASSOC_CONTENT_COUNT, 1);
- else {
- g_lpSessionManager->GetCacheManager()->UpdateCell(ulParentId, PR_CONTENT_COUNT, 1);
- struct propVal *lpPropMessageFlags = FindProp(lpModProps, PR_MESSAGE_FLAGS);
- if (lpPropMessageFlags && (lpPropMessageFlags->Value.ul & MSGFLAG_READ) == 0)
- // Unread message
- g_lpSessionManager->GetCacheManager()->UpdateCell(ulParentId, PR_CONTENT_UNREAD, 1);
- }
-
- g_lpSessionManager->NotificationModified(MAPI_FOLDER, ulParentId);
- if (ulGrandParentId) {
- g_lpSessionManager->GetCacheManager()->GetObjectFlags(ulParentId, &ulParentFlags);
- g_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, ulParentFlags & MSGFLAG_NOTIFY_FLAGS, ulGrandParentId, ulParentId, MAPI_FOLDER);
- }
- } else if (ulObjType == MAPI_STORE) {
- g_lpSessionManager->NotificationModified(ulObjType, ulObjId);
- } else {
- // Notify that the message has been modified
- if (ulObjType == MAPI_MESSAGE)
- g_lpSessionManager->NotificationModified(ulObjType, ulObjId, ulParentId);
- else
- g_lpSessionManager->NotificationModified(ulObjType, ulObjId);
- if (ulParentId)
- g_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, ulObjFlags & MSGFLAG_NOTIFY_FLAGS, ulParentId, ulObjId, ulObjType);
- }
- }
- return erSuccess;
- }
- ECRESULT WriteSingleProp(ECDatabase *lpDatabase, unsigned int ulObjId, unsigned int ulFolderId, struct propVal *lpPropVal, bool bColumnProp, unsigned int ulMaxQuerySize, std::string &strInsertQuery)
- {
- ECRESULT er;
- std::string strColData;
- std::string strQueryAppend;
- unsigned int ulColId = 0;
-
- assert(PROP_TYPE(lpPropVal->ulPropTag) != PT_UNICODE);
- er = CopySOAPPropValToDatabasePropVal(lpPropVal, &ulColId, strColData, lpDatabase, bColumnProp);
- if(er != erSuccess)
- return erSuccess; // Data from client was bogus, ignore it.
- if (!strInsertQuery.empty())
- strQueryAppend = ",";
- else if (bColumnProp)
- strQueryAppend = "REPLACE INTO tproperties (hierarchyid,tag,type,folderid," + (std::string)PROPCOLVALUEORDER(tproperties) + ") VALUES";
- else
- strQueryAppend = "REPLACE INTO properties (hierarchyid,tag,type," + (std::string)PROPCOLVALUEORDER(properties) + ") VALUES";
-
- strQueryAppend += "(" + stringify(ulObjId) + "," +
- stringify(PROP_ID(lpPropVal->ulPropTag)) + "," +
- stringify(PROP_TYPE(lpPropVal->ulPropTag)) + ",";
- if (bColumnProp)
- strQueryAppend += stringify(ulFolderId) + ",";
- for (unsigned int k = 0; k < VALUE_NR_MAX; ++k) {
- if (k == ulColId)
- strQueryAppend += strColData;
- else if (k == VALUE_NR_HILO)
- strQueryAppend += "null,null";
- else
- strQueryAppend += "null";
- if (k != VALUE_NR_MAX-1)
- strQueryAppend += ",";
- }
- strQueryAppend += ")";
- if (ulMaxQuerySize > 0 && strInsertQuery.size() + strQueryAppend.size() > ulMaxQuerySize)
- return KCERR_TOO_BIG;
- strInsertQuery.append(strQueryAppend);
- return erSuccess;
- }
- ECRESULT WriteProp(ECDatabase *lpDatabase, unsigned int ulObjId, unsigned int ulParentId, struct propVal *lpPropVal) {
- ECRESULT er;
- std::string strQuery;
-
- strQuery.clear();
- WriteSingleProp(lpDatabase, ulObjId, ulParentId, lpPropVal, false, 0, strQuery);
- er = lpDatabase->DoInsert(strQuery);
- if(er != erSuccess)
- return er;
-
- if(ulParentId > 0) {
- strQuery.clear();
- WriteSingleProp(lpDatabase, ulObjId, ulParentId, lpPropVal, true, 0, strQuery);
- er = lpDatabase->DoInsert(strQuery);
- if(er != erSuccess)
- return er;
- }
- return erSuccess;
- }
- ECRESULT GetNamesFromIDs(struct soap *soap, ECDatabase *lpDatabase, struct propTagArray *lpPropTags, struct namedPropArray *lpsNames)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- DB_LENGTHS lpDBLen = NULL;
- std::string strQuery;
- // Allocate memory for return object
- lpsNames->__ptr = s_alloc<namedProp>(soap, lpPropTags->__size);
- lpsNames->__size = lpPropTags->__size;
- memset(lpsNames->__ptr, 0, sizeof(struct namedProp) * lpPropTags->__size);
- for (gsoap_size_t i = 0; i < lpPropTags->__size; ++i) {
- strQuery = "SELECT nameid, namestring, guid FROM names WHERE id=" + stringify(lpPropTags->__ptr[i]-1) + " LIMIT 1";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- if(lpDatabase->GetNumRows(lpDBResult) == 1) {
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
- if(lpDBRow != NULL) {
- if(lpDBRow[0] != NULL) {
- // It's an ID type
- lpsNames->__ptr[i].lpId = s_alloc<unsigned int>(soap);
- *lpsNames->__ptr[i].lpId = atoi(lpDBRow[0]);
- } else if(lpDBRow[1] != NULL) {
- // It's a String type
- lpsNames->__ptr[i].lpString = s_alloc<char>(soap, strlen(lpDBRow[1])+1);
- strcpy(lpsNames->__ptr[i].lpString, lpDBRow[1]);
- }
- if(lpDBRow[2] != NULL) {
- // Got a GUID (should always do so ...)
- lpsNames->__ptr[i].lpguid = s_alloc<struct xsd__base64Binary>(soap);
- lpsNames->__ptr[i].lpguid->__size = lpDBLen[2];
- lpsNames->__ptr[i].lpguid->__ptr = s_alloc<unsigned char>(soap, lpDBLen[2]);
- memcpy(lpsNames->__ptr[i].lpguid->__ptr, lpDBRow[2], lpDBLen[2]);
- }
- } else {
- ec_log_crit("GetNamesFromIDs(): row/col NULL");
- return KCERR_DATABASE_ERROR;
- }
- } else {
- // No entry
- lpsNames->__ptr[i].lpguid = NULL;
- lpsNames->__ptr[i].lpId = NULL;
- lpsNames->__ptr[i].lpString = NULL;
- }
- }
- return erSuccess;
- }
- /**
- * Resets the folder counts of a folder
- *
- * This function resets the counts of a folder by recalculating them from the actual
- * database child entries. If any of the current counts is out-of-date, they are updated to the
- * correct value and the foldercount_reset counter is increased. Note that in theory, foldercount_reset
- * should always remain at 0. If not, this means that a bug somewhere has failed to update the folder
- * count correctly at some point.
- *
- * WARNING this function creates its own transaction!
- *
- * @param[in] lpSession Session to get database handle, etc
- * @param[in] ulObjId ID of the folder to recalc
- * @param[out] lpulUpdates Will be set to number of counters that were updated (may be NULL)
- * @return result
- */
- ECRESULT ResetFolderCount(ECSession *lpSession, unsigned int ulObjId, unsigned int *lpulUpdates)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- std::string strQuery;
- string strCC; // Content count
- string strACC; // Assoc. content count
- string strDMC; // Deleted message count
- string strDAC; // Deleted assoc message count
- string strCFC; // Child folder count
- string strDFC; // Deleted folder count
- string strCU; // Content unread
-
- unsigned int ulAffected = 0;
- unsigned int ulParent = 0;
-
- ECDatabase *lpDatabase = NULL;
- er = lpSession->GetDatabase(&lpDatabase);
- if(er != erSuccess)
- goto exit;
- er = lpDatabase->Begin();
- if(er != erSuccess)
- goto exit;
- // Lock the counters now since the locking order is normally counters/foldercontent/storesize/localcommittimemax. So our lock order
- // is now counters/foldercontent/counters which is compatible (*cough* in theory *cough*)
- strQuery = "SELECT val_ulong FROM properties WHERE hierarchyid = " + stringify(ulObjId) + " FOR UPDATE";
- er = lpDatabase->DoSelect(strQuery, NULL); // don't care about the result
- if (er != erSuccess)
- goto exit;
-
- // Gets counters from hierarchy: cc, acc, dmc, dac, cfc, dfc
- // use for update, since the update query below must see the same values, mysql should already block here.
- 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";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if (er != erSuccess)
- goto exit;
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if(lpDBRow == NULL || lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL || lpDBRow[3] == NULL || lpDBRow[4] == NULL) {
- er = KCERR_DATABASE_ERROR;
- ec_log_crit("ResetFolderCount(): row/col NULL (1)");
- goto exit;
- }
-
- strCC = lpDBRow[0];
- strACC = lpDBRow[1];
- strDMC = lpDBRow[2];
- strDAC = lpDBRow[3];
- strCFC = lpDBRow[4];
- strDFC = lpDBRow[5];
-
- // Gets unread counters from hierarchy / properties / tproperties
- strQuery = "SELECT "
- // Part one, unread count from non-deferred rows (get the flags from tproperties)
- "(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)"
- " + "
- // Part two, unread count from deferred rows (get the flags from properties)
- "(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)"
- ;
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if (er != erSuccess)
- goto exit;
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if(lpDBRow == NULL || lpDBRow[0] == NULL) {
- er = KCERR_DATABASE_ERROR;
- ec_log_crit("ResetFolderCount(): row/col NULL (2)");
- goto exit;
- }
-
- strCU = lpDBRow[0];
- strQuery = "UPDATE properties SET val_ulong = CASE tag "
- " WHEN " + stringify(PROP_ID(PR_CONTENT_COUNT)) + " THEN + " + strCC +
- " WHEN " + stringify(PROP_ID(PR_ASSOC_CONTENT_COUNT)) + " THEN + " + strACC +
- " WHEN " + stringify(PROP_ID(PR_DELETED_MSG_COUNT)) + " THEN + " + strDMC +
- " WHEN " + stringify(PROP_ID(PR_DELETED_ASSOC_MSG_COUNT)) + " THEN + " + strDAC +
- " WHEN " + stringify(PROP_ID(PR_FOLDER_CHILD_COUNT)) + " THEN + " + strCFC +
- " WHEN " + stringify(PROP_ID(PR_SUBFOLDERS)) + " THEN + " + strCFC +
- " WHEN " + stringify(PROP_ID(PR_DELETED_FOLDER_COUNT)) + " THEN + " + strDFC +
- " WHEN " + stringify(PROP_ID(PR_CONTENT_UNREAD)) + " THEN + " + strCU +
- " END WHERE hierarchyid = " + stringify(ulObjId) + " AND TAG in (" +
- stringify(PROP_ID(PR_CONTENT_COUNT)) + "," +
- stringify(PROP_ID(PR_ASSOC_CONTENT_COUNT)) + "," +
- stringify(PROP_ID(PR_DELETED_MSG_COUNT)) + "," +
- stringify(PROP_ID(PR_DELETED_ASSOC_MSG_COUNT)) + "," +
- stringify(PROP_ID(PR_FOLDER_CHILD_COUNT)) + "," +
- stringify(PROP_ID(PR_SUBFOLDERS)) + "," +
- stringify(PROP_ID(PR_DELETED_FOLDER_COUNT)) + "," +
- stringify(PROP_ID(PR_CONTENT_UNREAD)) +
- ")";
-
- er = lpDatabase->DoUpdate(strQuery, &ulAffected);
- if(er != erSuccess)
- goto exit;
- if (ulAffected == 0)
- // Nothing updated
- goto exit;
-
- // Trigger an assertion since in practice this should never happen
- // assert(false);
- g_lpStatsCollector->Increment(SCN_DATABASE_COUNTER_RESYNCS);
- er = lpSession->GetSessionManager()->GetCacheManager()->GetParent(ulObjId, &ulParent);
- if(er != erSuccess) {
- // No parent -> root folder. Nothing else we need to do now.
- er = erSuccess;
- goto exit;
- }
-
- // Update tprops
- strQuery = "REPLACE INTO tproperties (folderid, hierarchyid, tag, type, val_ulong) "
- "SELECT " + stringify(ulParent) + ", properties.hierarchyid, properties.tag, properties.type, properties.val_ulong "
- "FROM properties "
- "WHERE tag IN (" +
- stringify(PROP_ID(PR_CONTENT_COUNT)) + "," +
- stringify(PROP_ID(PR_ASSOC_CONTENT_COUNT)) + "," +
- stringify(PROP_ID(PR_DELETED_MSG_COUNT)) + "," +
- stringify(PROP_ID(PR_DELETED_ASSOC_MSG_COUNT)) + "," +
- stringify(PROP_ID(PR_FOLDER_CHILD_COUNT)) + "," +
- stringify(PROP_ID(PR_SUBFOLDERS)) + "," +
- stringify(PROP_ID(PR_DELETED_FOLDER_COUNT)) + "," +
- stringify(PROP_ID(PR_CONTENT_UNREAD)) +
- ") AND hierarchyid = " + stringify(ulObjId);
-
- er = lpDatabase->DoInsert(strQuery);
- if(er != erSuccess)
- goto exit;
-
- // Clear cache and update table entries. We do not send an object notification since the object hasn't really changed and
- // this is normally called just before opening an entry anyway, so the counters retrieved there will be ok.
- lpSession->GetSessionManager()->GetCacheManager()->Update(fnevObjectModified, ulObjId);
- er = lpSession->GetSessionManager()->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, ulObjId, MAPI_FOLDER);
- if(er != erSuccess)
- goto exit;
-
-
- exit:
- if(er != erSuccess)
- lpDatabase->Rollback();
- else {
- lpDatabase->Commit();
- if (lpulUpdates)
- *lpulUpdates = ulAffected;
- }
-
- return er;
- }
- /**
- * Removes stale indexed properties
- *
- * In some cases, the database can contain stale (old) indexed properties. One example is when
- * you replicate a store onto a server, then remove that store with kopano-admin --remove-store
- * and then do the replication again. The second replication will attempt to create items with
- * equal entryids and sourcekeys. Since the softdelete purge will not have removed the data from
- * the system yet, we check to see if the indexedproperty that is in the database is actually in
- * use by checking if the store it belongs to is deleted. If so, we remove the entry. If the
- * item is used by a non-deleted store, then an error occurs since you cannot use the same indexed
- * property for two items.
- *
- * @param[in] lpDatabase Database handle
- * @param[in] ulPropTag Property tag to scan for
- * @param[in] lpData Data if the indexed property
- * @param[in] cbSize Bytes in lpData
- * @return result
- */
- ECRESULT RemoveStaleIndexedProp(ECDatabase *lpDatabase, unsigned int ulPropTag, unsigned char *lpData, unsigned int cbSize)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- std::string strQuery;
- unsigned int ulObjId = 0;
- unsigned int ulStoreId = 0;
- bool bStale = false;
- strQuery = "SELECT hierarchyid FROM indexedproperties WHERE tag= " + stringify(PROP_ID(ulPropTag)) + " AND val_binary=" + lpDatabase->EscapeBinary(lpData, cbSize) + " LIMIT 1";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if(!lpDBRow || lpDBRow[0] == NULL)
- return er; /* Nothing there, no need to do anything */
-
- ulObjId = atoui(lpDBRow[0]);
-
- // Check if the found item is in a deleted store
- if(g_lpSessionManager->GetCacheManager()->GetStore(ulObjId, &ulStoreId, NULL) == erSuccess) {
- // Find the store
- strQuery = "SELECT hierarchy_id FROM stores WHERE hierarchy_id = " + stringify(ulStoreId) + " LIMIT 1";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if (lpDBRow == nullptr || lpDBRow[0] == nullptr)
- bStale = true;
- } else {
- // 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.
- bStale = true;
- }
- if(bStale) {
- // Item is in a deleted store. This means we can delete it
- er = lpDatabase->DoDelete("DELETE FROM indexedproperties WHERE hierarchyid = " + stringify(ulObjId));
- if(er != erSuccess)
- return er;
- // Remove it from the cache
- g_lpSessionManager->GetCacheManager()->RemoveIndexData(ulPropTag, cbSize, lpData);
- }
- else {
- ec_log_crit("RemoveStaleIndexedProp(): caller wanted to remove the entry, but we cannot since it is in use");
- return KCERR_COLLISION;
- }
- return erSuccess;
- }
- ECRESULT ApplyFolderCounts(ECDatabase *lpDatabase, unsigned int ulFolderId, const PARENTINFO &pi) {
- ECRESULT er;
-
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_CONTENT_COUNT, pi.lItems);
- if (er == erSuccess)
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_CONTENT_UNREAD, pi.lUnread);
- if (er == erSuccess)
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_ASSOC_CONTENT_COUNT, pi.lAssoc);
- if (er == erSuccess)
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_DELETED_MSG_COUNT, pi.lDeleted);
- if (er == erSuccess)
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_DELETED_ASSOC_MSG_COUNT, pi.lDeletedAssoc);
- if (er == erSuccess)
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_SUBFOLDERS, pi.lFolders);
- if (er == erSuccess)
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_FOLDER_CHILD_COUNT, pi.lFolders);
- if (er == erSuccess)
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_DELETED_FOLDER_COUNT, pi.lDeletedFolders);
- return er;
- }
- ECRESULT ApplyFolderCounts(ECDatabase *lpDatabase, const std::map<unsigned int, PARENTINFO> &mapFolderCounts) {
- ECRESULT er;
-
- // Update folder counts
- for (const auto &p : mapFolderCounts) {
- er = ApplyFolderCounts(lpDatabase, p.first, p.second);
- if (er != erSuccess)
- return er;
- }
- return erSuccess;
- }
- static ECRESULT LockFolders(ECDatabase *lpDatabase, bool bShared,
- const std::set<unsigned int> &setParents)
- {
- std::string strQuery;
- if(setParents.empty())
- return erSuccess;
-
- strQuery = "SELECT * FROM properties WHERE hierarchyid IN(";
-
- for (auto pa_id : setParents) {
- strQuery += stringify(pa_id);
- strQuery += ",";
- }
- strQuery.resize(strQuery.size()-1);
- strQuery += ") ";
-
- if (bShared)
- strQuery += "LOCK IN SHARE MODE";
- else
- strQuery += "FOR UPDATE";
-
- return lpDatabase->DoSelect(strQuery, NULL);
- }
- static ECRESULT BeginLockFolders(ECDatabase *lpDatabase, unsigned int ulTag,
- const std::set<std::string> &setIds, unsigned int ulFlags)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- std::set<unsigned int> setMessages;
- std::set<unsigned int> setFolders;
- std::set<std::string> setUncached;
- std::set<unsigned int> setUncachedMessages;
- unsigned int ulId;
- std::string strQuery;
-
- // See if we can get the object IDs for the passed objects from the cache
- for (const auto &s : setIds) {
- if (g_lpSessionManager->GetCacheManager()->QueryObjectFromProp(ulTag, s.size(),
- reinterpret_cast<unsigned char *>(const_cast<char *>(s.data())), &ulId) != erSuccess) {
- setUncached.insert(s);
- continue;
- }
- if (ulTag == PROP_ID(PR_SOURCE_KEY)) {
- setFolders.insert(ulId);
- } else if (ulTag != PROP_ID(PR_ENTRYID)) {
- assert(false);
- continue;
- }
- EntryId eid(s);
- try {
- if (eid.type() == MAPI_FOLDER)
- setFolders.insert(ulId);
- else if (eid.type() == MAPI_MESSAGE)
- setMessages.insert(ulId);
- else
- assert(false);
- } catch (runtime_error &e) {
- ec_log_err("eid.type(): %s\n", e.what());
- assert(false);
- }
- }
- if(!setUncached.empty()) {
- // For the items that were uncached, go directly to their parent (or the item itself for folders) in the DB
- strQuery = "SELECT hierarchyid, hierarchy.type, hierarchy.parent FROM indexedproperties JOIN hierarchy ON hierarchy.id=indexedproperties.hierarchyid WHERE tag = " + stringify(ulTag) + " AND val_binary IN(";
- for (auto i = setUncached.cbegin(); i != setUncached.cend(); ++i) {
- if (i != setUncached.cbegin())
- strQuery += ",";
- strQuery += lpDatabase->EscapeBinary(*i);
- }
- strQuery += ")";
-
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
-
- while((lpDBRow = lpDatabase->FetchRow(lpDBResult))) {
- if(lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL)
- continue;
-
- if(atoui(lpDBRow[1]) == MAPI_MESSAGE)
- setFolders.insert(atoui(lpDBRow[2]));
- else if(atoui(lpDBRow[1]) == MAPI_FOLDER)
- setFolders.insert(atoui(lpDBRow[0]));
- }
- }
-
- // For the items that were cached, but messages, find their parents in the cache first
- for (const auto i : setMessages) {
- unsigned int ulParent = 0;
-
- if (g_lpSessionManager->GetCacheManager()->QueryParent(i, &ulParent) == erSuccess)
- setFolders.insert(ulParent);
- else
- setUncachedMessages.insert(i);
- }
-
- // Query uncached parents from the database
- if(!setUncachedMessages.empty()) {
- strQuery = "SELECT parent FROM hierarchy WHERE id IN(";
- for (auto i = setUncachedMessages.cbegin();
- i != setUncachedMessages.cend(); ++i) {
- if(i != setUncachedMessages.begin())
- strQuery += ",";
- strQuery += stringify(*i);
- }
- strQuery += ")";
-
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- while((lpDBRow = lpDatabase->FetchRow(lpDBResult))) {
- if(lpDBRow[0] == NULL)
- continue;
-
- setFolders.insert(atoui(lpDBRow[0]));
- }
- }
-
- // Query objectid -> parentid for messages
- if (setFolders.empty())
- // No objects found that we can lock, fail.
- return KCERR_NOT_FOUND;
- er = lpDatabase->Begin();
- if(er != erSuccess)
- return er;
- return LockFolders(lpDatabase, ulFlags & LOCK_SHARED, setFolders);
- }
- /**
- * Begin a new transaction and lock folders
- *
- * Sourcekey of folders should be passed in setFolders.
- *
- */
- ECRESULT BeginLockFolders(ECDatabase *lpDatabase, const std::set<SOURCEKEY>& setFolders, unsigned int ulFlags)
- {
- std::set<std::string> setIds;
-
- std::copy(setFolders.begin(), setFolders.end(), std::inserter(setIds, setIds.begin()));
- return BeginLockFolders(lpDatabase, PROP_ID(PR_SOURCE_KEY), setIds, ulFlags);
- }
- /**
- * Begin a new transaction and lock folders
- *
- * EntryID of messages and folders to lock can be passed in setEntryIds. In practice, only the folders
- * in which the messages reside are locked.
- */
- ECRESULT BeginLockFolders(ECDatabase *lpDatabase, const std::set<EntryId>& setEntryIds, unsigned int ulFlags)
- {
- std::set<std::string> setIds;
-
- std::copy(setEntryIds.begin(), setEntryIds.end(), std::inserter(setIds, setIds.begin()));
- return BeginLockFolders(lpDatabase, PROP_ID(PR_ENTRYID), setIds, ulFlags);
- }
- ECRESULT BeginLockFolders(ECDatabase *lpDatabase, const EntryId &entryid, unsigned int ulFlags)
- {
- std::set<EntryId> set;
-
- // No locking needed for stores
- try {
- if (entryid.type() == MAPI_STORE)
- return lpDatabase->Begin();
- } catch (runtime_error &e) {
- ec_log_err("entryid.type(): %s\n", e.what());
- return KCERR_INVALID_PARAMETER;
- }
-
- set.insert(entryid);
-
- return BeginLockFolders(lpDatabase, set, ulFlags);
- }
- ECRESULT BeginLockFolders(ECDatabase *lpDatabase, const SOURCEKEY &sourcekey, unsigned int ulFlags)
- {
- return BeginLockFolders(lpDatabase, std::set<SOURCEKEY>({sourcekey}), ulFlags);
- }
- // Prepares child property data. This can be passed to ReadProps(). This allows the properties of child objects of object ulObjId to be
- // 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
- // children of an object.
- ECRESULT PrepareReadProps(struct soap *soap, ECDatabase *lpDatabase, bool fDoQuery, bool fUnicode, unsigned int ulObjId, unsigned int ulParentId, unsigned int ulMaxSize, ChildPropsMap *lpChildProps, NamedPropDefMap *lpNamedPropDefs)
- {
- ECRESULT er = erSuccess;
- unsigned int ulSize;
- struct propVal sPropVal;
- unsigned int ulChildId;
- ECStringCompat stringCompat(fUnicode);
- std::string strQuery;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- DB_LENGTHS lpDBLen = NULL;
- if (ulObjId == 0 && ulParentId == 0)
- return KCERR_INVALID_PARAMETER;
- if(fDoQuery) {
- // although we don't always use the names columns, we need to join anyway to check for existing nameids
- // we may never stream propids > 0x8500 without the names data
- if (ulObjId != 0)
- strQuery = "SELECT " PROPCOLORDER ", hierarchyid, names.nameid, names.namestring, names.guid "
- "FROM properties FORCE INDEX (PRIMARY) ";
- else
- strQuery = "SELECT " PROPCOLORDER ", hierarchy.id, names.nameid, names.namestring, names.guid "
- "FROM properties FORCE INDEX (PRIMARY) "
- "JOIN hierarchy FORCE INDEX (parenttypeflags) "
- "ON properties.hierarchyid=hierarchy.id ";
- strQuery +=
- "LEFT JOIN names "
- "ON (properties.tag-0x8501)=names.id ";
- if (ulObjId)
- strQuery += "WHERE hierarchyid=" + stringify(ulObjId);
- else
- strQuery += "WHERE hierarchy.parent=" + stringify(ulParentId);
- strQuery += " AND (tag <= 0x8500 OR names.id IS NOT NULL)";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- } else {
- er = lpDatabase->GetNextResult(&lpDBResult);
- if(er != erSuccess)
- return er;
- }
- while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
- unsigned int ulPropTag;
-
- lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
- if(lpDBLen == NULL) {
- ec_log_crit("PrepareReadProps(): FetchRowLengths failed");
- return KCERR_DATABASE_ERROR; /* this should never happen */
- }
- ulPropTag = PROP_TAG(atoi(lpDBRow[FIELD_NR_TYPE]),atoi(lpDBRow[FIELD_NR_TAG]));
-
- if (PROP_ID(ulPropTag) > 0x8500 && lpNamedPropDefs) {
- std::pair<NamedPropDefMap::iterator, bool> resInsert = lpNamedPropDefs->insert(NamedPropDefMap::value_type(ulPropTag, NAMEDPROPDEF()));
- if (resInsert.second) {
- // New entry
- if (lpDBLen[FIELD_NR_NAMEGUID] != sizeof(resInsert.first->second.guid)) {
- ec_log_err("PrepareReadProps(): record size mismatch");
- return KCERR_DATABASE_ERROR;
- }
- memcpy(&resInsert.first->second.guid, lpDBRow[FIELD_NR_NAMEGUID], sizeof(resInsert.first->second.guid));
-
- if (lpDBRow[FIELD_NR_NAMEID] != NULL) {
- resInsert.first->second.ulKind = MNID_ID;
- resInsert.first->second.ulId = atoui((char*)lpDBRow[FIELD_NR_NAMEID]);
- } else if (lpDBRow[FIELD_NR_NAMESTR] != NULL) {
- resInsert.first->second.ulKind = MNID_STRING;
- resInsert.first->second.strName.assign((char*)lpDBRow[FIELD_NR_NAMESTR], lpDBLen[FIELD_NR_NAMESTR]);
- } else {
- return KCERR_INVALID_TYPE;
- }
- }
- }
- // server strings are always unicode, for unicode clients.
- if (fUnicode) {
- if (PROP_TYPE(ulPropTag) == PT_STRING8)
- ulPropTag = CHANGE_PROP_TYPE(ulPropTag, PT_UNICODE);
- else if (PROP_TYPE(ulPropTag) == PT_MV_STRING8)
- ulPropTag = CHANGE_PROP_TYPE(ulPropTag, PT_MV_UNICODE);
- }
- ulChildId = atoui(lpDBRow[FIELD_NR_MAX]);
- auto iterChild = lpChildProps->find(ulChildId);
- if (iterChild == lpChildProps->cend()) {
- CHILDPROPS sChild;
-
- sChild.lpPropTags = new DynamicPropTagArray(soap);
- sChild.lpPropVals = new DynamicPropValArray(soap, 20);
-
- // First property for this child
- iterChild = lpChildProps->insert(ChildPropsMap::value_type(ulChildId, sChild)).first;
- }
-
- er = iterChild->second.lpPropTags->AddPropTag(ulPropTag);
- if(er != erSuccess)
- return er;
- er = GetPropSize(lpDBRow, lpDBLen, &ulSize);
- if(er == erSuccess && (ulMaxSize == 0 || ulSize < ulMaxSize)) {
- // the size of this property is small enough to send in the initial loading sequence
-
- er = CopyDatabasePropValToSOAPPropVal(soap, lpDBRow, lpDBLen, &sPropVal);
- if(er != erSuccess)
- continue;
- er = FixPropEncoding(soap, stringCompat, Out, &sPropVal);
- if (er != erSuccess)
- continue;
-
- iterChild->second.lpPropVals->AddPropVal(sPropVal);
- if (!soap)
- FreePropVal(&sPropVal, false);
- }
- }
- if(fDoQuery) {
- if (ulObjId != 0)
- strQuery = "SELECT " MVPROPCOLORDER ", hierarchyid, names.nameid, names.namestring, names.guid "
- "FROM mvproperties ";
- else
- strQuery = "SELECT " MVPROPCOLORDER ", hierarchy.id, names.nameid, names.namestring, names.guid "
- "FROM mvproperties "
- "JOIN hierarchy "
- "ON mvproperties.hierarchyid=hierarchy.id ";
- strQuery +=
- "LEFT JOIN names "
- "ON (mvproperties.tag-0x8501)=names.id ";
- if (ulObjId != 0)
- strQuery += "WHERE hierarchyid=" + stringify(ulObjId) +
- " AND (tag <= 0x8500 OR names.id IS NOT NULL) "
- " GROUP BY hierarchyid, tag";
- else
- strQuery += "WHERE hierarchy.parent=" + stringify(ulParentId) +
- " AND (tag <= 0x8500 OR names.id IS NOT NULL) "
- "GROUP BY tag, mvproperties.type";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- } else {
- er = lpDatabase->GetNextResult(&lpDBResult);
- if(er != erSuccess)
- return er;
- }
-
- // Do MV props
- while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
- lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
- if(lpDBLen == NULL) {
- ec_log_crit("PrepareReadProps(): FetchRowLengths failed(2)");
- return KCERR_DATABASE_ERROR; /* this should never happen */
- }
-
- if (lpNamedPropDefs) {
- unsigned int ulPropTag = PROP_TAG(atoi(lpDBRow[FIELD_NR_TYPE]),atoi(lpDBRow[FIELD_NR_TAG]));
- if (PROP_ID(ulPropTag) > 0x8500) {
- std::pair<NamedPropDefMap::iterator, bool> resInsert = lpNamedPropDefs->insert(NamedPropDefMap::value_type(ulPropTag, NAMEDPROPDEF()));
- if (resInsert.second) {
- // New entry
- if (lpDBLen[FIELD_NR_NAMEGUID] != sizeof(resInsert.first->second.guid)) {
- ec_log_crit("PrepareReadProps(): record size mismatch(2)");
- return KCERR_DATABASE_ERROR;
- }
- memcpy(&resInsert.first->second.guid, lpDBRow[FIELD_NR_NAMEGUID], sizeof(resInsert.first->second.guid));
-
- if (lpDBRow[FIELD_NR_NAMEID] != NULL) {
- resInsert.first->second.ulKind = MNID_ID;
- resInsert.first->second.ulId = atoui((char*)lpDBRow[FIELD_NR_NAMEID]);
- } else if (lpDBRow[FIELD_NR_NAMESTR] != NULL) {
- resInsert.first->second.ulKind = MNID_STRING;
- resInsert.first->second.strName.assign((char*)lpDBRow[FIELD_NR_NAMESTR], lpDBLen[FIELD_NR_NAMESTR]);
- } else {
- return KCERR_INVALID_TYPE;
- }
- }
- }
- }
- ulChildId = atoui(lpDBRow[FIELD_NR_MAX]);
- auto iterChild = lpChildProps->find(ulChildId);
- if (iterChild == lpChildProps->cend()) {
- CHILDPROPS sChild;
-
- sChild.lpPropTags = new DynamicPropTagArray(soap);
- sChild.lpPropVals = new DynamicPropValArray(soap, 20);
-
- // First property for this child
- iterChild = lpChildProps->insert(ChildPropsMap::value_type(ulChildId, sChild)).first;
- }
-
- er = CopyDatabasePropValToSOAPPropVal(soap, lpDBRow, lpDBLen, &sPropVal);
- if(er != erSuccess)
- continue;
- er = FixPropEncoding(soap, stringCompat, Out, &sPropVal);
- if (er != erSuccess)
- continue;
-
- er = iterChild->second.lpPropTags->AddPropTag(sPropVal.ulPropTag);
- if(er != erSuccess)
- continue;
- iterChild->second.lpPropVals->AddPropVal(sPropVal);
- if (!soap)
- FreePropVal(&sPropVal, false);
- }
- return erSuccess;
- }
- ECRESULT FreeChildProps(std::map<unsigned int, CHILDPROPS> *lpChildProps)
- {
- for (const auto &cp : *lpChildProps) {
- if (cp.second.lpPropVals)
- delete cp.second.lpPropVals;
- if (cp.second.lpPropTags)
- delete cp.second.lpPropTags;
- }
- lpChildProps->clear();
-
- return erSuccess;
- }
- } /* namespace */
|