123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653 |
- /*
- * 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 <memory>
- #include <new> // std::bad_alloc
- #include <list> // std::list
- #include "ArchiveControlImpl.h"
- #include "ECArchiverLogger.h"
- #include "ArchiverSession.h"
- #include "ArchiveStateCollector.h"
- #include "ArchiveStateUpdater.h"
- #include <kopano/userutil.h>
- #include <kopano/mapiext.h>
- #include "helpers/StoreHelper.h"
- #include "operations/copier.h"
- #include "operations/deleter.h"
- #include "operations/stubber.h"
- #include <kopano/ECConfig.h>
- #include "ECIterators.h"
- #include <kopano/ECRestriction.h>
- #include "HrException.h"
- #include "ArchiveManage.h"
- #include <kopano/MAPIErrors.h>
- #include <kopano/charset/convert.h>
- using namespace KC::helpers;
- using namespace KC::operations;
- namespace KC {
- /**
- * Create a new Archive object.
- *
- * @param[in] lpSession
- * Pointer to the Session.
- * @param[in] lpConfig
- * Pointer to an ECConfig object that determines the operational options.
- * @param[in] lpLogger
- * Pointer to an ECLogger object that's used for logging.
- * @param[in] bForceCleanup Force a cleanup operation to continue, even
- * if the settings aren't safe.
- * @param[out] lpptrArchiver
- * Pointer to a ArchivePtr that will be assigned the address of the returned object.
- */
- HRESULT ArchiveControlImpl::Create(ArchiverSessionPtr ptrSession, ECConfig *lpConfig, ECLogger *lpLogger, bool bForceCleanup, ArchiveControlPtr *lpptrArchiveControl)
- {
- std::unique_ptr<ArchiveControlImpl> ptrArchiveControl(
- new(std::nothrow) ArchiveControlImpl(ptrSession, lpConfig, lpLogger, bForceCleanup));
- if (ptrArchiveControl == nullptr)
- return MAPI_E_NOT_ENOUGH_MEMORY;
- HRESULT hr = ptrArchiveControl->Init();
- if (hr != hrSuccess)
- return hr;
- *lpptrArchiveControl = std::move(ptrArchiveControl);
- return hrSuccess;
- }
- /**
- * @param[in] lpSession
- * Pointer to the Session.
- * @param[in] lpConfig
- * Pointer to an ECConfig object that determines the operational options.
- * @param[in] lpLogger
- * Pointer to an ECLogger object that's used for logging.
- * @param[in] bForceCleanup Force a cleanup operation to continue, even
- * if the settings aren't safe.
- */
- ArchiveControlImpl::ArchiveControlImpl(ArchiverSessionPtr ptrSession, ECConfig *lpConfig, ECLogger *lpLogger, bool bForceCleanup)
- : m_ptrSession(ptrSession)
- , m_lpConfig(lpConfig)
- , m_lpLogger(new ECArchiverLogger(lpLogger))
- , m_cleanupAction(caStore)
- , m_bForceCleanup(bForceCleanup), __propmap(5)
- {
- }
- ArchiveControlImpl::~ArchiveControlImpl()
- {
- m_lpLogger->Release();
- }
- /**
- * Initialize the Archiver object.
- */
- HRESULT ArchiveControlImpl::Init()
- {
- m_bArchiveEnable = parseBool(m_lpConfig->GetSetting("archive_enable", "", "no"));
- m_ulArchiveAfter = atoi(m_lpConfig->GetSetting("archive_after", "", "30"));
- m_bDeleteEnable = parseBool(m_lpConfig->GetSetting("delete_enable", "", "no"));
- m_bDeleteUnread = parseBool(m_lpConfig->GetSetting("delete_unread", "", "no"));
- m_ulDeleteAfter = atoi(m_lpConfig->GetSetting("delete_after", "", "0"));
- m_bStubEnable = parseBool(m_lpConfig->GetSetting("stub_enable", "", "no"));
- m_bStubUnread = parseBool(m_lpConfig->GetSetting("stub_unread", "", "no"));
- m_ulStubAfter = atoi(m_lpConfig->GetSetting("stub_after", "", "0"));
- m_bPurgeEnable = parseBool(m_lpConfig->GetSetting("purge_enable", "", "no"));
- m_ulPurgeAfter = atoi(m_lpConfig->GetSetting("purge_after", "", "2555"));
- const char *lpszCleanupAction = m_lpConfig->GetSetting("cleanup_action");
- if (lpszCleanupAction == NULL || *lpszCleanupAction == '\0') {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Empty cleanup_action specified in config.");
- return MAPI_E_INVALID_PARAMETER;
- }
- if (strcasecmp(lpszCleanupAction, "delete") == 0)
- m_cleanupAction = caDelete;
- else if (strcasecmp(lpszCleanupAction, "store") == 0)
- m_cleanupAction = caStore;
- else if (strcasecmp(lpszCleanupAction, "none") == 0)
- m_cleanupAction = caNone;
- else {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Unknown cleanup_action specified in config: '%s'", lpszCleanupAction);
- return MAPI_E_INVALID_PARAMETER;
- }
- m_bCleanupFollowPurgeAfter = parseBool(m_lpConfig->GetSetting("cleanup_follow_purge_after", "", "no"));
- GetSystemTimeAsFileTime(&m_ftCurrent);
- return hrSuccess;
- }
- /**
- * Archive messages for all users. Optionaly only user that have their store on the server
- * to which the archiver is connected will have their messages archived.
- *
- * @param[in] bLocalOnly
- * If set to true only messsages for users that have their store on the local server
- * will be archived.
- */
- eResult ArchiveControlImpl::ArchiveAll(bool bLocalOnly, bool bAutoAttach, unsigned int ulFlags)
- {
- HRESULT hr = hrSuccess;
- if (ulFlags != ArchiveManage::Writable &&
- ulFlags != ArchiveManage::ReadOnly && ulFlags != 0)
- return MAPIErrorToArchiveError(MAPI_E_INVALID_PARAMETER);
- if (bAutoAttach || parseBool(m_lpConfig->GetSetting("enable_auto_attach"))) {
- ArchiveStateCollectorPtr ptrArchiveStateCollector;
- ArchiveStateUpdaterPtr ptrArchiveStateUpdater;
- hr = ArchiveStateCollector::Create(m_ptrSession, m_lpLogger, &ptrArchiveStateCollector);
- if (hr != hrSuccess)
- return MAPIErrorToArchiveError(hr);
- hr = ptrArchiveStateCollector->GetArchiveStateUpdater(&ptrArchiveStateUpdater);
- if (hr != hrSuccess)
- return MAPIErrorToArchiveError(hr);
- if (ulFlags == 0) {
- if (parseBool(m_lpConfig->GetSetting("auto_attach_writable")))
- ulFlags = ArchiveManage::Writable;
- else
- ulFlags = ArchiveManage::ReadOnly;
- }
- hr = ptrArchiveStateUpdater->UpdateAll(ulFlags);
- if (hr != hrSuccess)
- return MAPIErrorToArchiveError(hr);
- }
- return MAPIErrorToArchiveError(ProcessAll(bLocalOnly, &ArchiveControlImpl::DoArchive));
- }
- /**
- * Archive the messages of a particular user.
- *
- * @param[in] strUser
- * The username for which to archive the messages.
- */
- eResult ArchiveControlImpl::Archive(const tstring &strUser, bool bAutoAttach, unsigned int ulFlags)
- {
- HRESULT hr = hrSuccess;
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): function entry.");
- ScopedUserLogging sul(m_lpLogger, strUser);
- if (ulFlags != ArchiveManage::Writable && ulFlags != ArchiveManage::ReadOnly && ulFlags != 0) {
- hr = MAPI_E_INVALID_PARAMETER;
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "ArchiveControlImpl::Archive(): invalid parameter.");
- goto exit;
- }
- if (bAutoAttach || parseBool(m_lpConfig->GetSetting("enable_auto_attach"))) {
- ArchiveStateCollectorPtr ptrArchiveStateCollector;
- ArchiveStateUpdaterPtr ptrArchiveStateUpdater;
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): about to create collector.");
- hr = ArchiveStateCollector::Create(m_ptrSession, m_lpLogger, &ptrArchiveStateCollector);
- if (hr != hrSuccess)
- goto exit;
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): about to get updater.");
- hr = ptrArchiveStateCollector->GetArchiveStateUpdater(&ptrArchiveStateUpdater);
- if (hr != hrSuccess)
- goto exit;
- if (ulFlags == 0) {
- if (parseBool(m_lpConfig->GetSetting("auto_attach_writable")))
- ulFlags = ArchiveManage::Writable;
- else
- ulFlags = ArchiveManage::ReadOnly;
- }
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): about to update store of user %ls. Flags: 0x%08X", strUser.c_str(), ulFlags);
- hr = ptrArchiveStateUpdater->Update(strUser, ulFlags);
- if (hr != hrSuccess)
- goto exit;
- }
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive(): about to do real archive run.");
- hr = DoArchive(strUser);
- exit:
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "ArchiveControlImpl::Archive() at exit. Return code before transformation: 0x%08x (%s).", hr, GetMAPIErrorMessage(hr));
- return MAPIErrorToArchiveError(hr);
- }
- /**
- * Cleanup the archive(s) of all users. Optionaly only user that have their store on the server
- * to which the archiver is connected will have their messages archived.
- *
- * @param[in] bLocalOnly
- * If set to true only messsages for users that have their store on the local server
- * will be archived.
- */
- eResult ArchiveControlImpl::CleanupAll(bool bLocalOnly)
- {
- HRESULT hr = hrSuccess;
- hr = CheckSafeCleanupSettings();
- if (hr == hrSuccess)
- hr = ProcessAll(bLocalOnly, &ArchiveControlImpl::DoCleanup);
- return MAPIErrorToArchiveError(hr);
- }
- /**
- * Cleanup the archive(s) of a particular user.
- * Cleaning up is currently defined as detecting which messages were deleted
- * from the primary store and moving the archives of those messages to the
- * special deleted folder.
- *
- * @param[in] strUser
- * The username for which to archive the messages.
- */
- eResult ArchiveControlImpl::Cleanup(const tstring &strUser)
- {
- HRESULT hr = hrSuccess;
- ScopedUserLogging sul(m_lpLogger, strUser);
-
- hr = CheckSafeCleanupSettings();
- if (hr == hrSuccess)
- hr = DoCleanup(strUser);
- return MAPIErrorToArchiveError(hr);
- }
- /**
- * Process all users.
- *
- * @param[in] bLocalOnly Limit to users that have a store on the local server.
- * @param[in] fnProcess The method to execute to do the actual processing.
- */
- HRESULT ArchiveControlImpl::ProcessAll(bool bLocalOnly, fnProcess_t fnProcess)
- {
- typedef std::list<tstring> StringList;
-
- HRESULT hr = hrSuccess;
- StringList lstUsers;
- UserList lstUserEntries;
- bool bHaveErrors = false;
- hr = GetArchivedUserList(m_ptrSession->GetMAPISession(),
- m_ptrSession->GetSSLPath(), m_ptrSession->GetSSLPass(),
- &lstUsers, bLocalOnly);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to obtain user list. (hr=0x%08x)", hr);
- goto exit;
- }
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Processing %zu%s users.", lstUsers.size(), (bLocalOnly ? " local" : ""));
- for (const auto &user : lstUsers) {
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Processing user '" TSTRING_PRINTF "'.", user.c_str());
- HRESULT hrTmp = (this->*fnProcess)(user);
- if (FAILED(hrTmp)) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to process user '" TSTRING_PRINTF "'. (hr=0x%08x)", user.c_str(), hrTmp);
- bHaveErrors = true;
- } else if (hrTmp == MAPI_W_PARTIAL_COMPLETION) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Errors occurred while processing user '" TSTRING_PRINTF "'.", user.c_str());
- bHaveErrors = true;
- }
- }
- exit:
- if (hr == hrSuccess && bHaveErrors)
- hr = MAPI_W_PARTIAL_COMPLETION;
- return hr;
- }
- /**
- * Get the name of a folder from a IMAPIFolder object
- *
- * @param[in] folder Pointer to IMAPIFolder object to be queried
- * @return tstring containing folder name
- */
- tstring
- ArchiveControlImpl::getfoldername(LPMAPIFOLDER folder)
- {
- SPropValuePtr foldername;
- if (HrGetOneProp(folder, PR_DISPLAY_NAME, &~foldername) != hrSuccess)
- return convert_to<tstring>("<Unnamed>");
- return tstring(foldername->Value.LPSZ);
- }
- /**
- * Remove soft-deleted items from a IMAPIFolder object
- *
- * @param[in] folder Pointer to IMAPIFolder object to be queried
- * @param[in] strUser String constaining name of store
- * @return HRESULT. 0 on succes, error code on failure
- */
- HRESULT
- ArchiveControlImpl::purgesoftdeleteditems(LPMAPIFOLDER folder, const tstring& strUser)
- {
- HRESULT hr = hrSuccess;
- MAPITablePtr table;
- if ((hr = folder->GetContentsTable(SHOW_SOFT_DELETES, &~table)) != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get search folder contents table. (hr=%s)", stringify(hr, true).c_str());
- return hr;
- }
- static constexpr const SizedSPropTagArray(1, props) = {1, {PR_ENTRYID}};
- if ((hr = table->SetColumns(props, 0)) != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to set columns on table. (hr=%s)", stringify(hr, true).c_str());
- return hr;
- }
- unsigned int found = 0;
- unsigned int totalfound = 0;
- do {
- SRowSetPtr rowSet;
- if ((hr = table->QueryRows(100, 0, &rowSet)) != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get rows from table. (hr=%s)", stringify(hr, true).c_str());
- continue;
- }
- found = rowSet.size();
- totalfound += found;
- EntryListPtr ptrEntryList;
- hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~ptrEntryList);
- if (hr != hrSuccess)
- continue;
- hr = MAPIAllocateMore(sizeof(SBinary), ptrEntryList, (LPVOID*)&ptrEntryList->lpbin);
- if (hr != hrSuccess)
- continue;
- ptrEntryList->cValues = 1;
- for (unsigned int i = 0; i < found; ++i) {
- ptrEntryList->lpbin[0].cb = rowSet->aRow[i].lpProps[0].Value.bin.cb;
- ptrEntryList->lpbin[0].lpb = rowSet->aRow[i].lpProps[0].Value.bin.lpb;
- if ((hr = folder->DeleteMessages(ptrEntryList, 0, NULL, DELETE_HARD_DELETE)) != hrSuccess)
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to delete message. (hr=%s)", stringify(hr, true).c_str());
- }
- } while (found);
- if (totalfound)
- m_lpLogger->Log(
- EC_LOGLEVEL_INFO,
- "Store %ls: %u soft-deleted messages removed from folder %ls",
- strUser.c_str(),
- totalfound,
- getfoldername(folder).c_str()
- );
- return hr;
- }
- /**
- * Remove soft-deleted items of a user store
- *
- * @param[in] strUser tstring containing user name
- * @return HRESULT. 0 on succes, error code on failure
- */
- HRESULT
- ArchiveControlImpl::purgesoftdeletedmessages(const tstring& strUser)
- {
- MsgStorePtr store;
- HRESULT hr = m_ptrSession->OpenStoreByName(strUser, &~store);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to open user store. (hr=%s)", stringify(hr, true).c_str());
- return hr;
- }
- SPropValuePtr ptrPropValue;
- hr = HrGetOneProp(store, PR_IPM_SUBTREE_ENTRYID, &~ptrPropValue);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get PR_IPM_SUBTREE_ENTRYID. (hr=%s)", stringify(hr, true).c_str());
- return hr;
- }
- MAPIFolderPtr ipmSubtree;
- ULONG type = 0;
- if ((hr = store->OpenEntry(ptrPropValue->Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrPropValue->Value.bin.lpb), nullptr, MAPI_BEST_ACCESS | fMapiDeferredErrors, &type, &~ipmSubtree)) != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to open ipmSubtree. (hr=%s)", stringify(hr, true).c_str());
- return hr;
- }
- ECFolderIterator iEnd;
- for (auto i = ECFolderIterator(ipmSubtree, fMapiDeferredErrors, 0); i != iEnd; ++i)
- hr = purgesoftdeleteditems(*i, strUser);
- return hr;
- }
- /**
- * Perform the actual archive operation for a specific user.
- *
- * @param[in] strUser tstring containing user name
- */
- HRESULT ArchiveControlImpl::DoArchive(const tstring& strUser)
- {
- HRESULT hr = hrSuccess;
- MsgStorePtr ptrUserStore;
- StoreHelperPtr ptrStoreHelper;
- MAPIFolderPtr ptrSearchArchiveFolder;
- MAPIFolderPtr ptrSearchDeleteFolder;
- MAPIFolderPtr ptrSearchStubFolder;
- ObjectEntryList lstArchives;
- bool bHaveErrors = false;
- CopierPtr ptrCopyOp;
- DeleterPtr ptrDeleteOp;
- StubberPtr ptrStubOp;
- if (strUser.empty()) {
- hr = MAPI_E_INVALID_PARAMETER;
- goto exitpm;
- }
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Archiving store for user '" TSTRING_PRINTF "'", strUser.c_str());
- hr = m_ptrSession->OpenStoreByName(strUser, &~ptrUserStore);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to open store. (hr=%s)", stringify(hr, true).c_str());
- goto exitpm;
- }
- PROPMAP_INIT_NAMED_ID(ARCHIVE_STORE_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidStoreEntryIds)
- PROPMAP_INIT_NAMED_ID(ARCHIVE_ITEM_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidItemEntryIds)
- PROPMAP_INIT_NAMED_ID(ORIGINAL_SOURCEKEY, PT_BINARY, PSETID_Archive, dispidOrigSourceKey)
- PROPMAP_INIT_NAMED_ID(STUBBED, PT_BOOLEAN, PSETID_Archive, dispidStubbed)
- PROPMAP_INIT_NAMED_ID(DIRTY, PT_BOOLEAN, PSETID_Archive, dispidDirty)
- PROPMAP_INIT(ptrUserStore)
- hr = StoreHelper::Create(ptrUserStore, &ptrStoreHelper);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to create store helper. (hr=%s)", stringify(hr, true).c_str());
- goto exitpm;
- }
- hr = ptrStoreHelper->GetArchiveList(&lstArchives);
- if (hr != hrSuccess) {
- if (hr == MAPI_E_CORRUPT_DATA) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "List of archives is corrupt for user '" TSTRING_PRINTF "', skipping user.", strUser.c_str());
- hr = hrSuccess;
- } else
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get list of archives. (hr=%s)", stringify(hr, true).c_str());
- goto exitpm;
- }
- if (lstArchives.empty()) {
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "'" TSTRING_PRINTF "' has no attached archives", strUser.c_str());
- goto exitpm;
- }
- hr = ptrStoreHelper->GetSearchFolders(&~ptrSearchArchiveFolder, &~ptrSearchDeleteFolder, &~ptrSearchStubFolder);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get the search folders. (hr=%s)", stringify(hr, true).c_str());
- goto exitpm;
- }
- // Create and hook the three dependent steps
- if (m_bArchiveEnable && m_ulArchiveAfter >= 0) {
- SizedSPropTagArray(5, sptaExcludeProps) = {5, {PROP_ARCHIVE_STORE_ENTRYIDS, PROP_ARCHIVE_ITEM_ENTRYIDS, PROP_STUBBED, PROP_DIRTY, PROP_ORIGINAL_SOURCEKEY}};
- ptrCopyOp.reset(new Copier(m_ptrSession, m_lpConfig, m_lpLogger,
- lstArchives, sptaExcludeProps, m_ulArchiveAfter, true));
- }
- if (m_bDeleteEnable && m_ulDeleteAfter >= 0) {
- ptrDeleteOp.reset(new Deleter(m_lpLogger, m_ulDeleteAfter, m_bDeleteUnread));
- if (ptrCopyOp)
- ptrCopyOp->SetDeleteOperation(ptrDeleteOp);
- }
- if (m_bStubEnable && m_ulStubAfter >= 0) {
- ptrStubOp.reset(new Stubber(m_lpLogger, PROP_STUBBED, m_ulStubAfter, m_bStubUnread));
- if (ptrCopyOp)
- ptrCopyOp->SetStubOperation(ptrStubOp);
- }
- // Now execute them
- if (ptrCopyOp) {
- // Archive all unarchived messages that are old enough
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Archiving messages");
- hr = ProcessFolder(ptrSearchArchiveFolder, ptrCopyOp);
- if (FAILED(hr)) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to archive messages. (hr=%s)", stringify(hr, true).c_str());
- goto exitpm;
- } else if (hr == MAPI_W_PARTIAL_COMPLETION) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Some message could not be archived");
- bHaveErrors = true;
- hr = hrSuccess;
- }
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done archiving messages");
- }
- if (ptrDeleteOp) {
- // First delete all messages that are eligible for deletion, so we do not unneccesary stub them first
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Deleting old messages");
- hr = ProcessFolder(ptrSearchDeleteFolder, ptrDeleteOp);
- if (FAILED(hr)) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to delete old messages. (hr=%s)", stringify(hr, true).c_str());
- goto exitpm;
- } else if (hr == MAPI_W_PARTIAL_COMPLETION) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Some message could not be deleted");
- bHaveErrors = true;
- hr = hrSuccess;
- }
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done deleting messages");
- }
- if (ptrStubOp) {
- // Now stub the remaining messages (if they are old enough)
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Stubbing messages");
- hr = ProcessFolder(ptrSearchStubFolder, ptrStubOp);
- if (FAILED(hr)) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to stub messages. (hr=%s)", stringify(hr, true).c_str());
- goto exitpm;
- } else if (hr == MAPI_W_PARTIAL_COMPLETION) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Some message could not be stubbed");
- bHaveErrors = true;
- hr = hrSuccess;
- }
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done stubbing messages");
- }
- if (m_bPurgeEnable) {
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Purging archive(s)");
- hr = PurgeArchives(lstArchives);
- if (FAILED(hr)) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to purge archive(s). (hr=%s)", stringify(hr, true).c_str());
- goto exitpm;
- } else if (hr == MAPI_W_PARTIAL_COMPLETION) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Some archives could not be purged");
- bHaveErrors = true;
- hr = hrSuccess;
- }
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done purging archive(s)");
- }
- exitpm:
- if (hr == hrSuccess && bHaveErrors)
- hr = MAPI_W_PARTIAL_COMPLETION;
- return hr;
- }
- /**
- * Perform the actual cleanup operation for a specific user.
- *
- * @param[in] strUser tstring containing user name
- */
- HRESULT ArchiveControlImpl::DoCleanup(const tstring &strUser)
- {
- HRESULT hr;
- MsgStorePtr ptrUserStore;
- StoreHelperPtr ptrStoreHelper;
- ObjectEntryList lstArchives;
- SRestrictionPtr ptrRestriction;
- if (strUser.empty())
- return MAPI_E_INVALID_PARAMETER;
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Cleanup store for user '" TSTRING_PRINTF "', mode=%s", strUser.c_str(), m_lpConfig->GetSetting("cleanup_action"));
-
- if (m_bCleanupFollowPurgeAfter) {
- ULARGE_INTEGER li;
- SPropValue sPropRefTime;
- li.LowPart = m_ftCurrent.dwLowDateTime;
- li.HighPart = m_ftCurrent.dwHighDateTime;
-
- li.QuadPart -= (m_ulPurgeAfter * _DAY);
-
- sPropRefTime.ulPropTag = PROP_TAG(PT_SYSTIME, 0);
- sPropRefTime.Value.ft.dwLowDateTime = li.LowPart;
- sPropRefTime.Value.ft.dwHighDateTime = li.HighPart;
- hr = ECOrRestriction(
- ECAndRestriction(
- ECExistRestriction(PR_MESSAGE_DELIVERY_TIME) +
- ECPropertyRestriction(RELOP_LT, PR_MESSAGE_DELIVERY_TIME, &sPropRefTime, ECRestriction::Cheap)
- ) +
- ECAndRestriction(
- ECExistRestriction(PR_CLIENT_SUBMIT_TIME) +
- ECPropertyRestriction(RELOP_LT, PR_CLIENT_SUBMIT_TIME, &sPropRefTime, ECRestriction::Cheap)
- )
- ).CreateMAPIRestriction(&~ptrRestriction, 0);
- if (hr != hrSuccess)
- return hr;
- }
- hr = m_ptrSession->OpenStoreByName(strUser, &~ptrUserStore);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to open store. (hr=0x%08x)", hr);
- return hr;
- }
- hr = StoreHelper::Create(ptrUserStore, &ptrStoreHelper);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to create store helper. (hr=0x%08x)", hr);
- return hr;
- }
- hr = ptrStoreHelper->GetArchiveList(&lstArchives);
- if (hr != hrSuccess) {
- if (hr == MAPI_E_CORRUPT_DATA) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "List of archives is corrupt for user '" TSTRING_PRINTF "', skipping user.", strUser.c_str());
- hr = hrSuccess;
- } else
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get list of archives. (hr=0x%08x)", hr);
- return hr;
- }
- if (lstArchives.empty()) {
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "'" TSTRING_PRINTF "' has no attached archives", strUser.c_str());
- return hr;
- }
- for (const auto &arc : lstArchives) {
- HRESULT hrTmp = hrSuccess;
- hrTmp = CleanupArchive(arc, ptrUserStore, ptrRestriction);
- if (hrTmp != hrSuccess)
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to cleanup archive. (hr=0x%08x)", hr);
- }
- return hrSuccess;
- }
- /**
- * Process a search folder and place an additional restriction on it to get the messages
- * that should really be archived.
- *
- * @param[in] ptrFolder
- * A MAPIFolderPtr that points to the search folder to be processed.
- * @param[in] lpArchiveOperation
- * The pointer to a IArchiveOperation derived object that's used to perform
- * the actual processing.
- * @param[in] ulAge
- * The age in days since the message was delivered, that a message must be before
- * it will be processed.
- * @param[in] bProcessUnread
- * If set to true, unread messages will also be processed. Otherwise unread message
- * will be left untouched.
- */
- HRESULT ArchiveControlImpl::ProcessFolder(MAPIFolderPtr &ptrFolder, ArchiveOperationPtr ptrArchiveOperation)
- {
- HRESULT hr = hrSuccess;
- MAPITablePtr ptrTable;
- SRestrictionPtr ptrRestriction;
- SSortOrderSetPtr ptrSortOrder;
- SRowSetPtr ptrRowSet;
- MessagePtr ptrMessage;
- bool bHaveErrors = false;
- const tstring strFolderRestore = m_lpLogger->GetFolder();
- static constexpr const SizedSPropTagArray(3, sptaProps) =
- {3, {PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID}};
- hr = ptrFolder->GetContentsTable(fMapiDeferredErrors, &~ptrTable);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get search folder contents table. (hr=%s)", stringify(hr, true).c_str());
- goto exit;
- }
- hr = ptrTable->SetColumns(sptaProps, TBL_BATCH);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to set columns on table. (hr=%s)", stringify(hr, true).c_str());
- goto exit;
- }
- hr = ptrArchiveOperation->GetRestriction(ptrFolder, &~ptrRestriction);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get restriction from operation. (hr=%s)", stringify(hr, true).c_str());
- goto exit;
- }
- hr = ptrTable->Restrict(ptrRestriction, TBL_BATCH);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to set restriction on table. (hr=%s)", stringify(hr, true).c_str());
- goto exit;
- }
- hr = MAPIAllocateBuffer(CbNewSSortOrderSet(1), &~ptrSortOrder);
- if (hr != hrSuccess)
- goto exit;
- ptrSortOrder->cSorts = 1;
- ptrSortOrder->cCategories = 0;
- ptrSortOrder->cExpanded = 0;
- ptrSortOrder->aSort[0].ulPropTag = PR_PARENT_ENTRYID;
- ptrSortOrder->aSort[0].ulOrder = TABLE_SORT_ASCEND ;
- hr = ptrTable->SortTable(ptrSortOrder, TBL_BATCH);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to sort table. (hr=%s)", stringify(hr, true).c_str());
- goto exit;
- }
- do {
- hr = ptrTable->QueryRows(50, 0, &ptrRowSet);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get rows from table. (hr=%s)", stringify(hr, true).c_str());
- goto exit;
- }
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Processing batch of %u messages", ptrRowSet.size());
- for (ULONG i = 0; i < ptrRowSet.size(); ++i) {
- hr = ptrArchiveOperation->ProcessEntry(ptrFolder, ptrRowSet[i].cValues, ptrRowSet[i].lpProps);
- if (hr != hrSuccess) {
- bHaveErrors = true;
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to process entry. (hr=%s)", stringify(hr, true).c_str());
- if (hr == MAPI_E_STORE_FULL) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Disk full or over quota.");
- goto exit;
- }
- continue;
- }
- }
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Done processing batch");
- } while (ptrRowSet.size() == 50);
- exit:
- if (hr == hrSuccess && bHaveErrors)
- hr = MAPI_W_PARTIAL_COMPLETION;
- m_lpLogger->SetFolder(strFolderRestore);
- return hr;
- }
- /**
- * Purge a set of archives. Purging an archive is defined as deleting all
- * messages that are older than a set amount of days.
- *
- * @param[in] lstArchives The list of archives to purge.
- */
- HRESULT ArchiveControlImpl::PurgeArchives(const ObjectEntryList &lstArchives)
- {
- HRESULT hr = hrSuccess;
- bool bErrorOccurred = false;
- KCHL::memory_ptr<SRestriction> lpRestriction;
- SPropValue sPropCreationTime;
- ULARGE_INTEGER li;
- SRowSetPtr ptrRowSet;
- static constexpr const SizedSPropTagArray(2, sptaFolderProps) =
- {2, {PR_ENTRYID, PR_DISPLAY_NAME}};
- enum {IDX_ENTRYID, IDX_DISPLAY_NAME};
- // Create the common restriction that determines which messages are old enough to purge.
- li.LowPart = m_ftCurrent.dwLowDateTime;
- li.HighPart = m_ftCurrent.dwHighDateTime;
- li.QuadPart -= (m_ulPurgeAfter * _DAY);
- sPropCreationTime.ulPropTag = PR_MESSAGE_DELIVERY_TIME;
- sPropCreationTime.Value.ft.dwLowDateTime = li.LowPart;
- sPropCreationTime.Value.ft.dwHighDateTime = li.HighPart;
- hr = ECPropertyRestriction(RELOP_LT, PR_MESSAGE_DELIVERY_TIME, &sPropCreationTime, ECRestriction::Cheap)
- .CreateMAPIRestriction(&~lpRestriction, ECRestriction::Cheap);
- if (hr != hrSuccess)
- goto exit;
- for (const auto &arc : lstArchives) {
- MsgStorePtr ptrArchiveStore;
- MAPIFolderPtr ptrArchiveRoot;
- ULONG ulType = 0;
- MAPITablePtr ptrFolderTable;
- SRowSetPtr ptrFolderRows;
- hr = m_ptrSession->OpenStore(arc.sStoreEntryId, &~ptrArchiveStore);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open archive. (entryid=%s, hr=%s)", arc.sStoreEntryId.tostring().c_str(), stringify(hr, true).c_str());
- bErrorOccurred = true;
- continue;
- }
- // Purge root of archive
- hr = PurgeArchiveFolder(ptrArchiveStore, arc.sItemEntryId, lpRestriction);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to purge archive root. (entryid=%s, hr=%s)", arc.sItemEntryId.tostring().c_str(), stringify(hr, true).c_str());
- bErrorOccurred = true;
- continue;
- }
- // Get all subfolders and purge those as well.
- hr = ptrArchiveStore->OpenEntry(arc.sItemEntryId.size(), arc.sItemEntryId, &ptrArchiveRoot.iid(), MAPI_BEST_ACCESS|fMapiDeferredErrors, &ulType, &~ptrArchiveRoot);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open archive root. (entryid=%s, hr=%s)", arc.sItemEntryId.tostring().c_str(), stringify(hr, true).c_str());
- bErrorOccurred = true;
- continue;
- }
- hr = ptrArchiveRoot->GetHierarchyTable(CONVENIENT_DEPTH | fMapiDeferredErrors, &~ptrFolderTable);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get archive hierarchy table. (hr=%s)", stringify(hr, true).c_str());
- bErrorOccurred = true;
- continue;
- }
- hr = ptrFolderTable->SetColumns(sptaFolderProps, TBL_BATCH);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to select folder table columns. (hr=%s)", stringify(hr, true).c_str());
- bErrorOccurred = true;
- continue;
- }
- while (true) {
- hr = ptrFolderTable->QueryRows(50, 0, &ptrFolderRows);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get rows from folder table. (hr=%s)", stringify(hr, true).c_str());
- goto exit;
- }
- for (ULONG i = 0; i < ptrFolderRows.size(); ++i) {
- ScopedFolderLogging sfl(m_lpLogger, ptrFolderRows[i].lpProps[IDX_DISPLAY_NAME].ulPropTag == PR_DISPLAY_NAME ? ptrFolderRows[i].lpProps[IDX_DISPLAY_NAME].Value.LPSZ : _T("<Unnamed>"));
- hr = PurgeArchiveFolder(ptrArchiveStore, ptrFolderRows[i].lpProps[IDX_ENTRYID].Value.bin, lpRestriction);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to purge archive folder. (entryid=%s, hr=%s)", bin2hex(ptrFolderRows[i].lpProps[IDX_ENTRYID].Value.bin.cb, ptrFolderRows[i].lpProps[IDX_ENTRYID].Value.bin.lpb).c_str(), stringify(hr, true).c_str());
- bErrorOccurred = true;
- }
- }
- if (ptrFolderRows.size() < 50)
- break;
- }
- }
- exit:
- if (hr == hrSuccess && bErrorOccurred)
- hr = MAPI_W_PARTIAL_COMPLETION;
- return hr;
- }
- /**
- * Purge an archive folder.
- *
- * @param[in] ptrArchive The archive store containing the folder to purge.
- * @param[in] folderEntryID The entryid of the folder to purge.
- * @param[in] lpRestriction The restriction to use to determine which messages to delete.
- */
- HRESULT ArchiveControlImpl::PurgeArchiveFolder(MsgStorePtr &ptrArchive, const entryid_t &folderEntryID, const LPSRestriction lpRestriction)
- {
- HRESULT hr;
- ULONG ulType = 0;
- MAPIFolderPtr ptrFolder;
- MAPITablePtr ptrContentsTable;
- std::list<entryid_t> lstEntries;
- SRowSetPtr ptrRows;
- EntryListPtr ptrEntryList;
- ULONG ulIdx = 0;
- static constexpr const SizedSPropTagArray(1, sptaTableProps) = {1, {PR_ENTRYID}};
- hr = ptrArchive->OpenEntry(folderEntryID.size(), folderEntryID, &ptrFolder.iid(), MAPI_BEST_ACCESS | fMapiDeferredErrors, &ulType, &~ptrFolder);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open archive folder. (entryid=%s, hr=%s)", folderEntryID.tostring().c_str(), stringify(hr, true).c_str());
- return hr;
- }
- hr = ptrFolder->GetContentsTable(fMapiDeferredErrors, &~ptrContentsTable);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open contents table. (hr=%s)", stringify(hr, true).c_str());
- return hr;
- }
- hr = ptrContentsTable->SetColumns(sptaTableProps, TBL_BATCH);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to select table columns. (hr=%s)", stringify(hr, true).c_str());
- return hr;
- }
- hr = ptrContentsTable->Restrict(lpRestriction, TBL_BATCH);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to restrict contents table. (hr=%s)", stringify(hr, true).c_str());
- return hr;
- }
- while (true) {
- hr = ptrContentsTable->QueryRows(50, 0, &ptrRows);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to get rows from contents table. (hr=%s)", stringify(hr, true).c_str());
- return hr;
- }
- for (ULONG i = 0; i < ptrRows.size(); ++i)
- lstEntries.push_back(ptrRows[i].lpProps[0].Value.bin);
- if (ptrRows.size() < 50)
- break;
- }
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Purging %zu messaged from archive folder", lstEntries.size());
- hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~ptrEntryList);
- if (hr != hrSuccess)
- return hr;
- hr = MAPIAllocateMore(lstEntries.size() * sizeof(SBinary), ptrEntryList, (LPVOID*)&ptrEntryList->lpbin);
- if (hr != hrSuccess)
- return hr;
- ptrEntryList->cValues = lstEntries.size();
- for (const auto &e : lstEntries) {
- ptrEntryList->lpbin[ulIdx].cb = e.size();
- ptrEntryList->lpbin[ulIdx++].lpb = e;
- }
- hr = ptrFolder->DeleteMessages(ptrEntryList, 0, NULL, 0);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to delete %u messages. (hr=%s)", ptrEntryList->cValues, stringify(hr, true).c_str());
- return hr;
- }
- return hrSuccess;
- }
- /**
- * Cleanup an archive.
- *
- * @param[in] archiveEntry SObjectEntry specifyinf the archive to cleanup
- * @param[in] lpUserStore The primary store, used to check the references
- * @param[in] lpRestriction The restriction that's used to make sure the archived items are old enough.
- */
- HRESULT ArchiveControlImpl::CleanupArchive(const SObjectEntry &archiveEntry, IMsgStore* lpUserStore, LPSRestriction lpRestriction)
- {
- HRESULT hr;
- SPropValuePtr ptrPropVal;
- EntryIDSet setRefs;
- EntryIDSet setEntries;
- EntryIDSet setDead;
-
- ArchiveHelperPtr ptrArchiveHelper;
- MAPIFolderPtr ptrArchiveFolder;
- ECFolderIterator iEnd;
- hr = ArchiveHelper::Create(m_ptrSession, archiveEntry, m_lpLogger, &ptrArchiveHelper);
- if (hr != hrSuccess)
- return hr;
- hr = ptrArchiveHelper->GetArchiveFolder(true, &~ptrArchiveFolder);
- if (hr != hrSuccess)
- return hr;
- if (m_cleanupAction == caStore) {
- // If the cleanup action is store, we need to perform the hierarchy cleanup
- // before cleaning up messages so the hierarchy gets preserved.
- hr = CleanupHierarchy(ptrArchiveHelper, ptrArchiveFolder, lpUserStore);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to cleanup hierarchy.");
- return hr;
- }
- }
- // Get the archive store GUID (PR_STORE_RECORD_KEY)
- hr = HrGetOneProp(ptrArchiveHelper->GetMsgStore(), PR_STORE_RECORD_KEY, &~ptrPropVal);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get store GUID of archive store.");
- return hr;
- }
-
- if (ptrPropVal->Value.bin.cb != sizeof(GUID)) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Store record key size does not match that of a GUID. size=%u", ptrPropVal->Value.bin.cb);
- return MAPI_E_CORRUPT_DATA;
- }
-
- // Get a set of all primary messages that have a reference to this archive.
- hr = GetAllReferences(lpUserStore, (LPGUID)ptrPropVal->Value.bin.lpb, &setRefs);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get all references from primary store. (hr=0x%08x)", hr);
- return hr;
- }
- hr = GetAllEntries(ptrArchiveHelper, ptrArchiveFolder, lpRestriction, &setEntries);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get all entries from archive store. (hr=0x%08x)", hr);
- return hr;
- }
- // We now have a set containing the entryids of all messages in the archive and a set containing all
- // references to archives in the primary store, which are those same entryids.
- // We simply check which entries are in the set of entries from the archive and not in the set of
- // entries in the primary store. Those can be deleted (or stored).
-
- //The difference of two sets is formed by the elements that are present in the first set, but not in
- //the second one. Notice that this is a directional operation.
- std::set_difference(setEntries.begin(), setEntries.end(), setRefs.begin(), setRefs.end(), std::inserter(setDead, setDead.begin()));
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Found %zu dead entries in archive.", setDead.size());
-
- if (m_cleanupAction == caNone) {
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "cleanup_action is set to none, therefore skipping cleanup action.");
- return hr;
- }
- if (!setDead.empty()) {
- if (m_cleanupAction == caStore)
- hr = MoveAndDetachMessages(ptrArchiveHelper, ptrArchiveFolder, setDead);
- else
- hr = DeleteMessages(ptrArchiveFolder, setDead);
- }
- if (m_cleanupAction != caDelete)
- return hr;
- // If the cleanup action is delete, we need to cleanup the hierarchy after cleaning the
- // messages because we won't delete non-empty folders. So we want to get rid of the
- // messages first.
- hr = CleanupHierarchy(ptrArchiveHelper, ptrArchiveFolder, lpUserStore);
- if (hr != hrSuccess)
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to cleanup hierarchy.");
- return hr;
- }
- /**
- * Get all references to archived items from the primary store. A reference is the entryid of
- * the archived messages.
- *
- * @param[in] lpUserStore The primary store containing the references.
- * @param[in] lpArchiveGuid The GUID of the archive store for which to get the references.
- * @param[out] lpReferences An EntryIDSet containing all references.
- */
- HRESULT ArchiveControlImpl::GetAllReferences(LPMDB lpUserStore, LPGUID lpArchiveGuid, EntryIDSet *lpReferences)
- {
- HRESULT hr;
- EntryIDSet setRefs;
- SPropValuePtr ptrPropVal;
- ULONG ulType = 0;
- MAPIFolderPtr ptrIpmSubtree;
- ECFolderIterator iEnd;
- // Find the primary store IPM subtree
- hr = HrGetOneProp(lpUserStore, PR_IPM_SUBTREE_ENTRYID, &~ptrPropVal);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to locate ipm subtree of primary store. (hr=0x%08x)", hr);
- return hr;
- }
- hr = lpUserStore->OpenEntry(ptrPropVal->Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrPropVal->Value.bin.lpb), &ptrIpmSubtree.iid(), 0, &ulType, &~ptrIpmSubtree);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to open ipm subtree of primary store. (hr=0x%08x)", hr);
- return hr;
- }
-
- hr = AppendAllReferences(ptrIpmSubtree, lpArchiveGuid, &setRefs);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get all references from the ipm subtree. (hr=0x%08x)", hr);
- return hr;
- }
-
- try {
- for (ECFolderIterator i = ECFolderIterator(ptrIpmSubtree, fMapiDeferredErrors, 0); i != iEnd; ++i) {
- hr = AppendAllReferences(*i, lpArchiveGuid, &setRefs);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get all references from primary folder. (hr=0x%08x)", hr);
- return hr;
- }
- }
- } catch (const HrException &he) {
- hr = he.hr();
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to iterate primary folders. (hr=0x%08x)", hr);
- return hr;
- }
-
- lpReferences->swap(setRefs);
- return hrSuccess;
- }
- /**
- * Get all references to archived items from a primary folder and add them to the
- * passed set.
- *
- * @param[in] lpUserStore The primary store containing the references.
- * @param[in] lpArchiveGuid The GUID of the archive store for which to get the references.
- * @param[out] lpReferences The EntryIDSet to add the references to.
- */
- HRESULT ArchiveControlImpl::AppendAllReferences(LPMAPIFOLDER lpFolder, LPGUID lpArchiveGuid, EntryIDSet *lpReferences)
- {
- HRESULT hr = hrSuccess;
- BYTE prefixData[4 + sizeof(GUID)] = {0};
- static constexpr const ULONG ulFlagArray[] = {0, SHOW_SOFT_DELETES};
- SizedSPropTagArray(1, sptaContentProps) = {1, {PT_NULL}};
- PROPMAP_START(1)
- PROPMAP_NAMED_ID(ITEM_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidItemEntryIds)
- PROPMAP_INIT(lpFolder)
-
- sptaContentProps.aulPropTag[0] = PROP_ITEM_ENTRYIDS;
-
- memcpy(prefixData + 4, lpArchiveGuid, sizeof(GUID));
-
- for (size_t i = 0; i < ARRAY_SIZE(ulFlagArray); ++i) {
- MAPITablePtr ptrTable;
-
- hr = lpFolder->GetContentsTable(ulFlagArray[i], &~ptrTable);
- if (hr != hrSuccess)
- goto exitpm;
- hr = ptrTable->SetColumns(sptaContentProps, TBL_BATCH);
- if (hr != hrSuccess)
- goto exitpm;
-
- while (true) {
- SRowSetPtr ptrRows;
- const ULONG batch_size = 128;
-
- hr = ptrTable->QueryRows(batch_size, 0, &ptrRows);
- if (hr != hrSuccess)
- goto exitpm;
-
- for (SRowSetPtr::size_type j = 0; j < ptrRows.size(); ++j) {
- if (PROP_TYPE(ptrRows[j].lpProps[0].ulPropTag) == PT_ERROR)
- continue;
-
- for (ULONG k = 0; k < ptrRows[j].lpProps[0].Value.MVbin.cValues; ++k) {
- if (ptrRows[j].lpProps[0].Value.MVbin.lpbin[k].cb >= sizeof(prefixData) &&
- memcmp(ptrRows[j].lpProps[0].Value.MVbin.lpbin[k].lpb, prefixData, sizeof(prefixData)) == 0) {
- lpReferences->insert(ptrRows[j].lpProps[0].Value.MVbin.lpbin[k]);
- }
- }
- }
-
- if (ptrRows.size() < batch_size)
- break;
- }
- }
- exitpm:
- return hr;
- }
- /**
- * Get the entryid of almost all messages in an archive store. Everything that's
- * below the special folder root is excluded because those don't necessarily get
- * referenced from the primary store anymore.
- *
- * @param[in] ptrArchiveHelper The ArchiverHelper instance for the archive to process.
- * @param[in] lpArchive The root of the archive.
- * @param[in] lpRestriction The restriction that's used to make sure the archived items are old enough.
- * @param[out] lpEntryies An EntryIDSet containing all the entryids.
- */
- HRESULT ArchiveControlImpl::GetAllEntries(ArchiveHelperPtr ptrArchiveHelper, LPMAPIFOLDER lpArchive, LPSRestriction lpRestriction, EntryIDSet *lpEntries)
- {
- HRESULT hr;
- EntryIDSet setEntries;
- ECFolderIterator iEnd;
- EntryIDSet setFolderExcludes;
- MAPIFolderPtr ptrFolder;
- hr = AppendAllEntries(lpArchive, lpRestriction, &setEntries);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get all entries from the root archive folder. (hr=0x%08x)", hr);
- return hr;
- }
- // Exclude everything below the special folder root because that's were we store messages
- // that have not references to the primary store.
- hr = ptrArchiveHelper->GetSpecialsRootFolder(&~ptrFolder);
- if (hr == hrSuccess)
- hr = AppendFolderEntries(ptrFolder, &setFolderExcludes);
- ptrFolder.reset();
- try {
- for (ECFolderIterator i = ECFolderIterator(lpArchive, fMapiDeferredErrors, 0); i != iEnd; ++i) {
- SPropValuePtr ptrProp;
-
- hr = HrGetOneProp(*i, PR_ENTRYID, &~ptrProp);
- if (hr != hrSuccess)
- return hr;
-
- if (setFolderExcludes.find(ptrProp->Value.bin) != setFolderExcludes.end()) {
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Skipping special folder");
- continue;
- }
-
- hr = AppendAllEntries(*i, lpRestriction, &setEntries);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Unable to get all references from archive folder. (hr=0x%08x)", hr);
- return hr;
- }
- }
- } catch (const HrException &he) {
- hr = he.hr();
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to iterate archive folders. (hr=0x%08x)", hr);
- return hr;
- }
-
- lpEntries->swap(setEntries);
- return hrSuccess;
- }
- /**
- * Get the entryid of all messages in an archive folder and append them to the passed set.
- *
- * @param[in] ptrArchiveHelper The ArchiverHelper instance for the archive to process.
- * @param[in] lpArchive The root of the archive.
- * @param[in] lpRestriction The restriction that's used to make sure the archived items are old enough.
- * @param[in,out] lpEntryies The EntryIDSet to add the items to.
- */
- HRESULT ArchiveControlImpl::AppendAllEntries(LPMAPIFOLDER lpArchive, LPSRestriction lpRestriction, EntryIDSet *lpEntries)
- {
- HRESULT hr = hrSuccess;
- MAPITablePtr ptrTable;
- ECAndRestriction resContent;
- static constexpr const SizedSPropTagArray(1, sptaContentProps) = {1, {PR_ENTRYID}};
-
- PROPMAP_START(1)
- PROPMAP_NAMED_ID(REF_ITEM_ENTRYID, PT_BINARY, PSETID_Archive, dispidRefItemEntryId)
- PROPMAP_INIT(lpArchive)
-
- resContent += ECExistRestriction(PROP_REF_ITEM_ENTRYID);
- if (lpRestriction)
- resContent += ECRawRestriction(lpRestriction, ECRestriction::Cheap);
- hr = lpArchive->GetContentsTable(0, &~ptrTable);
- if (hr != hrSuccess)
- goto exitpm;
- hr = ptrTable->SetColumns(sptaContentProps, TBL_BATCH);
- if (hr != hrSuccess)
- goto exitpm;
- hr = resContent.RestrictTable(ptrTable);
- if (hr != hrSuccess)
- goto exitpm;
-
- while (true) {
- SRowSetPtr ptrRows;
- const ULONG batch_size = 128;
-
- hr = ptrTable->QueryRows(batch_size, 0, &ptrRows);
- if (hr != hrSuccess)
- goto exitpm;
-
- for (SRowSetPtr::size_type i = 0; i < ptrRows.size(); ++i) {
- if (PROP_TYPE(ptrRows[i].lpProps[0].ulPropTag) == PT_ERROR) {
- hr = ptrRows[i].lpProps[0].Value.err;
- goto exitpm;
- }
-
- lpEntries->insert(ptrRows[i].lpProps[0].Value.bin);
- }
-
- if (ptrRows.size() < batch_size)
- break;
- }
- exitpm:
- return hr;
- }
- /**
- * Cleanup the archive hierarchy. This works by going through the hierarchy
- * and check for each folder if the back reference still works. If it doesn't
- * the folder should be deleted or moved to the deleted items folder.
- * When the cleanup_action is 'delete', the folder will only be deleted if
- * it's empty.
- * If the cleanup action is 'store', the folder will be moved to the deleted
- * items folder as is, leaving the hierarchy in tact.
- *
- * @param[in] ptrArchiveHelper The ArchiverHelper instance for the archive to process.
- * @param[in] lpArchiveRoot The root of the archive.
- * @param[out] lpUserStore The users primary store.
- */
- HRESULT ArchiveControlImpl::CleanupHierarchy(ArchiveHelperPtr ptrArchiveHelper, LPMAPIFOLDER lpArchiveRoot, LPMDB lpUserStore)
- {
- HRESULT hr = hrSuccess;
- MAPITablePtr ptrTable;
- static constexpr const SizedSSortOrderSet(1, ssosHierarchy) = {1, 0, 0, {PR_DEPTH, TABLE_SORT_ASCEND}};
- SizedSPropTagArray(5, sptaHierarchyProps) = {5, {PR_NULL, PR_ENTRYID, PR_CONTENT_COUNT, PR_FOLDER_CHILD_COUNT, PR_DISPLAY_NAME}};
- enum {IDX_REF_ITEM_ENTRYID, IDX_ENTRYID, IDX_CONTENT_COUNT, IDX_FOLDER_CHILD_COUNT, IDX_DISPLAY_NAME};
-
- PROPMAP_START(1)
- PROPMAP_NAMED_ID(REF_ITEM_ENTRYID, PT_BINARY, PSETID_Archive, dispidRefItemEntryId)
- PROPMAP_INIT(lpArchiveRoot)
-
- sptaHierarchyProps.aulPropTag[IDX_REF_ITEM_ENTRYID] = PROP_REF_ITEM_ENTRYID;
- hr = lpArchiveRoot->GetHierarchyTable(CONVENIENT_DEPTH, &~ptrTable);
- if (hr != hrSuccess)
- goto exitpm;
- hr = ptrTable->SetColumns(sptaHierarchyProps, TBL_BATCH);
- if (hr != hrSuccess)
- goto exitpm;
- hr = ECExistRestriction(PROP_REF_ITEM_ENTRYID)
- .RestrictTable(ptrTable, TBL_BATCH);
- if (hr != hrSuccess)
- goto exitpm;
- hr = ptrTable->SortTable(ssosHierarchy, TBL_BATCH);
- if (hr != hrSuccess)
- goto exitpm;
-
- while (true) {
- SRowSetPtr ptrRows;
-
- hr = ptrTable->QueryRows(64, 0, &ptrRows);
- if (hr != hrSuccess)
- goto exitpm;
- if (ptrRows.empty())
- break;
-
- for (SRowSetPtr::size_type i = 0; i < ptrRows.size(); ++i) {
- ULONG ulType = 0;
- MAPIFolderPtr ptrPrimaryFolder;
- ScopedFolderLogging sfl(m_lpLogger, ptrRows[i].lpProps[IDX_DISPLAY_NAME].ulPropTag == PR_DISPLAY_NAME ? ptrRows[i].lpProps[IDX_DISPLAY_NAME].Value.LPSZ : _T("<Unnamed>"));
-
- // If the cleanup action is delete, we don't want to delete a folder that's not empty because it might contain messages that
- // have been moved in the primary store before the original folder was deleted. If we were to delete the folder in the archive
- // we would lose that data.
- // But if the cleanup action is store, we do want to move the folder with content so the hierarchy is preserved.
-
- if (m_cleanupAction == caDelete) {
- // The content count and folder child count should always exist. If not we'll skip the folder
- // just to be safe.
- if (PROP_TYPE(ptrRows[i].lpProps[IDX_CONTENT_COUNT].ulPropTag) == PT_ERROR) {
- m_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to obtain folder content count. Skipping folder. (hr=0x%08x)", ptrRows[i].lpProps[IDX_CONTENT_COUNT].Value.err);
- continue;
- } else if (ptrRows[i].lpProps[IDX_CONTENT_COUNT].Value.l != 0) {
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Folder contains messages. Skipping folder.");
- continue;
- }
-
- if (PROP_TYPE(ptrRows[i].lpProps[IDX_FOLDER_CHILD_COUNT].ulPropTag) == PT_ERROR) {
- m_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to obtain folder child count. Skipping folder. (hr=0x%08x)", ptrRows[i].lpProps[IDX_FOLDER_CHILD_COUNT].Value.err);
- continue;
- } else if (ptrRows[i].lpProps[IDX_FOLDER_CHILD_COUNT].Value.l != 0) {
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Folder has subfolders in it. Skipping folder.");
- continue;
- }
- }
-
- hr = lpUserStore->OpenEntry(ptrRows[i].lpProps[IDX_REF_ITEM_ENTRYID].Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrRows[i].lpProps[IDX_REF_ITEM_ENTRYID].Value.bin.lpb),
- &ptrPrimaryFolder.iid(), 0, &ulType, &~ptrPrimaryFolder);
- if (hr == MAPI_E_NOT_FOUND) {
- MAPIFolderPtr ptrArchiveFolder;
- SPropValuePtr ptrProp;
-
- hr = lpArchiveRoot->OpenEntry(ptrRows[i].lpProps[IDX_ENTRYID].Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrRows[i].lpProps[IDX_ENTRYID].Value.bin.lpb),
- &ptrArchiveFolder.iid(), MAPI_MODIFY, &ulType, &~ptrArchiveFolder);
- if (hr != hrSuccess)
- goto exitpm;
-
- // Check if we still have a back-ref
- if (HrGetOneProp(ptrArchiveFolder, PROP_REF_ITEM_ENTRYID, &~ptrProp) != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Back ref is gone. Folder is possibly moved to the deleted items already.");
- continue;
- }
-
- // The primary folder does not exist anymore and this folder is empty. Time to get rid of it.
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Primary folder seems to have been deleted");
- if (m_cleanupAction == caStore)
- hr = MoveAndDetachFolder(ptrArchiveHelper, ptrArchiveFolder);
- else
- hr = DeleteFolder(ptrArchiveFolder);
- if (hr != hrSuccess)
- m_lpLogger->Log(EC_LOGLEVEL_WARNING, "Unable to process dead folder. (hr=0x%08x)", hr);
- }
- if (hr != hrSuccess)
- goto exitpm;
- }
- }
- exitpm:
- return hr;
- }
- /**
- * Move a set of messages to the special 'Deleted Items' folder and remove their reference to a
- * primary message that was deleted.
- *
- * @param[in] ptrArchiveHelper An ArchiveHelper object containing the archive store that's being processed.
- * @param[in] lpArchiveFolder The archive folder containing the messages to move.
- * @param[in] setEIDs The set with entryids of the messages to process.
- */
- HRESULT ArchiveControlImpl::MoveAndDetachMessages(ArchiveHelperPtr ptrArchiveHelper, LPMAPIFOLDER lpArchiveFolder, const EntryIDSet &setEIDs)
- {
- HRESULT hr;
- MAPIFolderPtr ptrDelItemsFolder;
- EntryListPtr ptrMessageList;
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Moving %zu messages to the special 'Deleted Items' folder...", setEIDs.size());
- hr = ptrArchiveHelper->GetDeletedItemsFolder(&~ptrDelItemsFolder);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get deleted items folder. (hr=0x%08x)", hr);
- return hr;
- }
- hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~ptrMessageList);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to allocate %zu bytes of memory. (hr=0x%08x)", sizeof(ENTRYLIST), hr);
- return hr;
- }
- ptrMessageList->cValues = 0;
- hr = MAPIAllocateMore(sizeof(SBinary) * setEIDs.size(), ptrMessageList, (LPVOID*)&ptrMessageList->lpbin);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to allocate %zu bytes of memory. (hr=0x%08x)", sizeof(SBinary) * setEIDs.size(), hr);
- return hr;
- }
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Processing %zu messages", setEIDs.size());
- for (const auto &e : setEIDs) {
- ULONG ulType;
- MAPIPropPtr ptrMessage;
- MAPIPropHelperPtr ptrHelper;
- hr = lpArchiveFolder->OpenEntry(e.size(), e, &ptrMessage.iid(), MAPI_MODIFY, &ulType, &~ptrMessage);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to open message. (hr=0x%08x)", hr);
- return hr;
- }
- hr = MAPIPropHelper::Create(ptrMessage, &ptrHelper);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to create helper object. (hr=0x%08x)", hr);
- return hr;
- }
- hr = ptrHelper->ClearReference(true);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to clear back reference. (hr=0x%08x)", hr);
- return hr;
- }
- ptrMessageList->lpbin[ptrMessageList->cValues].cb = e.size();
- ptrMessageList->lpbin[ptrMessageList->cValues++].lpb = e;
- assert(ptrMessageList->cValues <= setEIDs.size());
- }
- hr = lpArchiveFolder->CopyMessages(ptrMessageList, &ptrDelItemsFolder.iid(), ptrDelItemsFolder, 0, NULL, MESSAGE_MOVE);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to move messages. (hr=0x%08x)", hr);
- return hr;
- }
- return hrSuccess;
- }
- /**
- * Move a folder to the special 'Deleted Items' folder and remove its reference to the
- * primary folder that was deleted.
- *
- * @param[in] ptrArchiveHelper An ArchiveHelper object containing the archive store that's being processed.
- * @param[in] lpArchiveFolder The archive folder to move.
- */
- HRESULT ArchiveControlImpl::MoveAndDetachFolder(ArchiveHelperPtr ptrArchiveHelper, LPMAPIFOLDER lpArchiveFolder)
- {
- HRESULT hr;
- SPropValuePtr ptrEntryID;
- MAPIFolderPtr ptrDelItemsFolder;
- MAPIPropHelperPtr ptrHelper;
- ECFolderIterator iEnd;
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Moving folder to the special 'Deleted Items' folder...");
- hr = HrGetOneProp(lpArchiveFolder, PR_ENTRYID, &~ptrEntryID);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get folder entryid. (hr=0x%08x)", hr);
- return hr;
- }
- hr = ptrArchiveHelper->GetDeletedItemsFolder(&~ptrDelItemsFolder);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get deleted items folder. (hr=0x%08x)", hr);
- return hr;
- }
- hr = MAPIPropHelper::Create(MAPIPropPtr(lpArchiveFolder, true), &ptrHelper);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to create helper object. (hr=0x%08x)", hr);
- return hr;
- }
- hr = ptrHelper->ClearReference(true);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to clear back reference. (hr=0x%08x)", hr);
- return hr;
- }
-
- // Get rid of references of all subfolders
- try {
- for (ECFolderIterator i = ECFolderIterator(lpArchiveFolder, fMapiDeferredErrors, 0); i != iEnd; ++i) {
- MAPIPropHelperPtr ptrSubHelper;
-
- hr = MAPIPropHelper::Create(MAPIPropPtr(*i, true), &ptrSubHelper);
- if (hr != hrSuccess)
- return hr;
-
- hr = ptrSubHelper->ClearReference(true);
- if (hr != hrSuccess)
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Failed to clean reference of subfolder.");
- }
- } catch (const HrException &he) {
- hr = he.hr();
- m_lpLogger->Log(EC_LOGLEVEL_FATAL, "Failed to iterate folders. (hr=0x%08x)", hr);
- return hr;
- }
- hr = lpArchiveFolder->CopyFolder(ptrEntryID->Value.bin.cb, (LPENTRYID)ptrEntryID->Value.bin.lpb, &ptrDelItemsFolder.iid(), ptrDelItemsFolder, NULL, 0, NULL, FOLDER_MOVE);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to move folder. (hr=0x%08x)", hr);
- return hr;
- }
- return hrSuccess;
- }
- /**
- * Delete the messages in setEIDs from the folder lpArchiveFolder.
- *
- * @param[in] lpArchiveFolder The folder to delete the messages from.
- * @param[in] setEIDs The set of entryids of the messages to delete.
- */
- HRESULT ArchiveControlImpl::DeleteMessages(LPMAPIFOLDER lpArchiveFolder, const EntryIDSet &setEIDs)
- {
- HRESULT hr;
- EntryListPtr ptrMessageList;
-
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Deleting %zu messages...", setEIDs.size());
- hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~ptrMessageList);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to allocate %zu bytes of memory. (hr=0x%08x)", sizeof(ENTRYLIST), hr);
- return hr;
- }
- ptrMessageList->cValues = 0;
- hr = MAPIAllocateMore(sizeof(SBinary) * setEIDs.size(), ptrMessageList, (LPVOID*)&ptrMessageList->lpbin);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to allocate %zu bytes of memory. (hr=0x%08x)", sizeof(SBinary) * setEIDs.size(), hr);
- return hr;
- }
- m_lpLogger->Log(EC_LOGLEVEL_DEBUG, "Processing %zu messages", setEIDs.size());
- for (const auto &e : setEIDs) {
- ptrMessageList->lpbin[ptrMessageList->cValues].cb = e.size();
- ptrMessageList->lpbin[ptrMessageList->cValues++].lpb = e;
- }
- return lpArchiveFolder->DeleteMessages(ptrMessageList, 0, NULL, 0);
- }
- /**
- * Delete the folder specified by lpArchiveFolder
- *
- * @param[in] lpArchiveFolder Folder to delete.
- */
- HRESULT ArchiveControlImpl::DeleteFolder(LPMAPIFOLDER lpArchiveFolder)
- {
- HRESULT hr;
- SPropValuePtr ptrEntryId;
-
- m_lpLogger->Log(EC_LOGLEVEL_INFO, "Deleting folder...");
- hr = HrGetOneProp(lpArchiveFolder, PR_ENTRYID, &~ptrEntryId);
- if (hr != hrSuccess) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to get folder entryid (hr=0x%08x)", hr);
- return hr;
- }
- // Delete yourself!
- hr = lpArchiveFolder->DeleteFolder(ptrEntryId->Value.bin.cb, (LPENTRYID)ptrEntryId->Value.bin.lpb, 0, NULL, DEL_FOLDERS|DEL_MESSAGES|DEL_ASSOCIATED);
- if (FAILED(hr)) {
- m_lpLogger->Log(EC_LOGLEVEL_ERROR, "Failed to delete folder (hr=0x%08x)", hr);
- return hr;
- } else if (hr != hrSuccess)
- m_lpLogger->Log(EC_LOGLEVEL_WARNING, "Folder only got partially deleted (hr=0x%08x)", hr);
- return hr;
- }
- /**
- * Append the entryid of the passed folder and all its subfolder to a list.
- * @param[in] lpBase The folder to start processing.
- * @param[out] lpEntries The returned set of entryids.
- */
- HRESULT ArchiveControlImpl::AppendFolderEntries(LPMAPIFOLDER lpBase, EntryIDSet *lpEntries)
- {
- HRESULT hr;
- SPropValuePtr ptrProp;
- MAPITablePtr ptrTable;
- static constexpr const SizedSPropTagArray(1, sptaTableProps) = {1, {PR_ENTRYID}};
-
- hr = HrGetOneProp(lpBase, PR_ENTRYID, &~ptrProp);
- if (hr != hrSuccess)
- return hr;
-
- lpEntries->insert(ptrProp->Value.bin);
- hr = lpBase->GetHierarchyTable(CONVENIENT_DEPTH, &~ptrTable);
- if (hr != hrSuccess)
- return hr;
- hr = ptrTable->SetColumns(sptaTableProps, TBL_BATCH);
- if (hr != hrSuccess)
- return hr;
-
- while (true) {
- SRowSetPtr ptrRows;
-
- hr = ptrTable->QueryRows(128, 0, &ptrRows);
- if (hr != hrSuccess)
- return hr;
- if (ptrRows.empty())
- break;
-
- for (SRowSetPtr::size_type i = 0; i < ptrRows.size(); ++i)
- lpEntries->insert(ptrRows[i].lpProps[0].Value.bin);
- }
- return hrSuccess;
- }
- /**
- * This method checks the settings to see if they're safe when performing
- * a cleanup run. It's unsafe to run a cleanup when the delete operation is
- * enabled, and the cleanup doesn't check the purge_after option or if the
- * purge_after option is set to 0.
- *
- * See ZCP-10571.
- */
- HRESULT ArchiveControlImpl::CheckSafeCleanupSettings()
- {
- int loglevel = (m_bForceCleanup ? EC_LOGLEVEL_WARNING : EC_LOGLEVEL_FATAL);
-
- if (m_bDeleteEnable && !m_bCleanupFollowPurgeAfter) {
- m_lpLogger->Log(loglevel, "'delete_enable' is set to '%s' and 'cleanup_follow_purge_after' is set to '%s'",
- m_lpConfig->GetSetting("delete_enable", "", "no"),
- m_lpConfig->GetSetting("cleanup_follow_purge_after", "", "no"));
- m_lpLogger->Log(loglevel, "This can cause messages to be deleted from the archive while they shouldn't be deleted.");
- if (!m_bForceCleanup) {
- m_lpLogger->Log(loglevel, "Please correct your configuration or pass '--force-cleanup' at the commandline if you");
- m_lpLogger->Log(loglevel, "know what you're doing (not recommended).");
- return MAPI_E_UNABLE_TO_COMPLETE;
- }
- m_lpLogger->Log(loglevel, "User forced continuation!");
- }
-
- else if (m_bDeleteEnable && m_bCleanupFollowPurgeAfter && m_ulPurgeAfter == 0) {
- m_lpLogger->Log(loglevel, "'delete_enable' is set to '%s' and 'cleanup_follow_purge_after' is set to '%s'",
- m_lpConfig->GetSetting("delete_enable", "", "no"),
- m_lpConfig->GetSetting("cleanup_follow_purge_after", "", "no"));
- m_lpLogger->Log(loglevel, "but 'purge_after' is set to '0'");
- m_lpLogger->Log(loglevel, "This can cause messages to be deleted from the archive while they shouldn't be deleted.");
- if (!m_bForceCleanup) {
- m_lpLogger->Log(loglevel, "Please correct your configuration or pass '--force-cleanup' at the commandline if you");
- m_lpLogger->Log(loglevel, "know what you're doing (not recommended).");
- return MAPI_E_UNABLE_TO_COMPLETE;
- }
- m_lpLogger->Log(loglevel, "User forced continuation!");
- }
-
- return hrSuccess;
- }
- } /* namespace */
|