// --cfgnotif.c-----------------------------------------------------------------
//
// Routines to provide notification when a given extension data section has
// changed, and read the new data.
//
// Copyright (C) Microsoft Corp. 1986-1996. All Rights Reserved.
// -----------------------------------------------------------------------------
#include "edk.h"
#include "attrname.h"
#include "cfgnotif.chk"
//
// Internal Function Declarations
//
static DWORD WINAPI PollThread(
IN LPVOID lpvThreadParam);
static HRESULT HrUpdateAdviseNotify(
IN LPSPropValue lpNewData,
IN OUT LPADVISENOTIFY lpNotify);
static HRESULT HrDestroyAdviseNotify(
IN OUT LPADVISENOTIFY * lppNotify);
//
// Configuration API Routines
//
//$--HrCfgCreateAdviseObj-------------------------------------------------------
// Begin monitoring of a MAPI session for changes to extension data.
// This routine should be called once to create an advise object for a MAPI
// session that is being monitored. Then, HrCfgAdvise() should be called
// once for each extension data section that is being monitored.
// If nPollInterval == 0 then no monitoring thread is created, and the user
// must do polling manually by calling HrCfgPollAdviseObj().
// -----------------------------------------------------------------------------
HRESULT HrCfgCreateAdviseObj( // RETURNS: HRESULT
IN LPMAPISESSION lpSession, // MAPI session to monitor
IN ULONG nPollInterval, // milliseconds between polling
OUT LPADVISEOBJ * lppAdviseObj) // created advise object
{
HRESULT hr = NOERROR;
LPADVISEOBJ lpAdviseObj = NULL;
SECURITY_ATTRIBUTES saEvent = {0, NULL, TRUE};
BOOL fCriticalSectionInitialized = FALSE;
hr = CHK_HrCfgCreateAdviseObj(
lpSession,
nPollInterval,
lppAdviseObj);
if (FAILED(hr))
RETURN(hr);
DEBUGPUBLIC("HrCfgCreateAdviseObj()\n");
// Get memory for the advise object.
hr = MAPIAllocateBuffer(sizeof(*lpAdviseObj), &lpAdviseObj);
if (FAILED(hr))
goto cleanup;
ZeroMemory(lpAdviseObj, sizeof(*lpAdviseObj));
// Store parameters in advise object.
lpAdviseObj->lpSession = lpSession;
lpAdviseObj->nPollInterval = nPollInterval;
// Get an IMAPIProp interface on the current object.
hr = HrOpenSessionObject(lpSession, &lpAdviseObj->lpCurrentObject);
if (FAILED(hr))
goto cleanup;
// Get the property tag for USN-Changed.
hr = HrCfgPropertyTagFromNameId(
lpAdviseObj->lpCurrentObject,
NM_USN_CHANGED,
&lpAdviseObj->ulUSNChangedPropTag);
if (FAILED(hr))
goto cleanup;
// If it came back without the property type then set it to PT_LONG.
if (PROP_TYPE(lpAdviseObj->ulUSNChangedPropTag) == 0)
{
lpAdviseObj->ulUSNChangedPropTag =
CHANGE_PROP_TYPE(lpAdviseObj->ulUSNChangedPropTag,
PT_LONG);
}
// Get the property tag for Extension-Data.
hr = HrCfgPropertyTagFromNameId(
lpAdviseObj->lpCurrentObject,
NM_EXTENSION_DATA,
&lpAdviseObj->ulExtensionDataPropTag);
if (FAILED(hr))
goto cleanup;
// If it came back without the property type then set it to PT_MV_BINARY.
if (PROP_TYPE(lpAdviseObj->ulExtensionDataPropTag) == 0)
{
lpAdviseObj->ulExtensionDataPropTag =
CHANGE_PROP_TYPE(lpAdviseObj->ulExtensionDataPropTag,
PT_MV_BINARY);
}
// Create a critical section to synchronize access to the advise object.
InitializeCriticalSection(&lpAdviseObj->csCriticalSection);
fCriticalSectionInitialized = TRUE;
// "Poll" the object to get the initial values of the extension data blobs.
hr = HrCfgPollAdviseObj(lpAdviseObj);
if (FAILED(hr))
goto cleanup;
// Only create the thread if nPollInterval != 0.
if (nPollInterval != 0)
{
// Create an event to stop the polling thread.
lpAdviseObj->hPollThreadStop = CreateEvent(&saEvent, FALSE, FALSE, NULL);
if (lpAdviseObj->hPollThreadStop == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
// Create the polling thread.
lpAdviseObj->hPollThread = CreateThread(
NULL,
0,
PollThread,
lpAdviseObj,
0,
&lpAdviseObj->ulPollThreadID);
if (lpAdviseObj->hPollThread == NULL)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}
// Set the return variable.
*lppAdviseObj = lpAdviseObj;
cleanup:
if (FAILED(hr) && lpAdviseObj)
{
CLOSEHANDLE(lpAdviseObj->hPollThreadStop);
if (fCriticalSectionInitialized)
DeleteCriticalSection(&lpAdviseObj->csCriticalSection);
ULRELEASE(lpAdviseObj->lpCurrentObject);
MAPIFREEBUFFER(lpAdviseObj);
}
RETURN(hr);
}
//$--HrCfgPollAdviseObj---------------------------------------------------------
// Checks an advise object against what is stored in the DSA. Calls the
// notification routines if there are any changes.
// -----------------------------------------------------------------------------
HRESULT HrCfgPollAdviseObj( // RETURNS: HRESULT
IN LPADVISEOBJ lpAdviseObj) // advise object to poll
{
HRESULT hr = NOERROR;
ULONG ulNewUSNChanged = 0;
LPSPropValue lpNewExtensionData = NULL;
ULONG cValues = 0;
LPADVISENOTIFY lpAdviseNotify = NULL;
hr = CHK_HrCfgPollAdviseObj(
lpAdviseObj);
if (FAILED(hr))
RETURN(hr);
DEBUGPUBLIC("HrCfgPollAdviseObj()\n");
// Lock the object against access by other threads.
EnterCriticalSection(&lpAdviseObj->csCriticalSection);
// Get the new USN-Changed from the DSA. If the new USN-Changed is
// different from the one we have stored then something in the object
// has changed (possibly the extension data). Note: the first time these
// statements are executed, USN-Changed will always have "changed" since
// the one we have stored starts out at 0.
hr = HrMAPIGetPropLong(
lpAdviseObj->lpCurrentObject,
lpAdviseObj->ulUSNChangedPropTag,
&ulNewUSNChanged);
if (FAILED(hr))
goto cleanup;
// If USN-Changed is different then do the update.
if (ulNewUSNChanged != lpAdviseObj->ulUSNChanged)
{
SizedSPropTagArray(1, ExtDataTag) =
{1, {lpAdviseObj->ulExtensionDataPropTag}};
// Read the new extension data.
hr = MAPICALL(lpAdviseObj->lpCurrentObject)->GetProps(
lpAdviseObj->lpCurrentObject,
(LPSPropTagArray) &ExtDataTag,
0,
&cValues,
&lpNewExtensionData);
// If the property was not found then allocate an empty multivalued
// binary property and store it as if it had been read from the
// extension data property.
if (hr == MAPI_W_ERRORS_RETURNED &&
cValues == 1 &&
PROP_TYPE(lpNewExtensionData->ulPropTag) == PT_ERROR &&
lpNewExtensionData->Value.err == MAPI_E_NOT_FOUND)
{
lpNewExtensionData->ulPropTag = lpAdviseObj->ulExtensionDataPropTag;
lpNewExtensionData->Value.MVbin.cValues = 0;
hr = MAPIAllocateMore(
0,
lpNewExtensionData,
&lpNewExtensionData->Value.MVbin.lpbin);
if (FAILED(hr))
goto cleanup;
hr = NOERROR;
}
// If there was any other error then return.
if (FAILED(hr) || hr == MAPI_W_ERRORS_RETURNED)
goto cleanup;
// Loop for each ADVISENOTIFY and check to see if it's data has changed.
// If it has, update it and call the callback routine. After this
// loop is done, the blob pointers in the ADVISENOTIFY structures will
// point into the new extension data property, not the old one. Then,
// the old one can be freed and replaced in the ADVISEOBJ structure
// by the new one.
for (
lpAdviseNotify = lpAdviseObj->lpNotifyList;
lpAdviseNotify;
lpAdviseNotify = lpAdviseNotify->lpNext)
{
hr = HrUpdateAdviseNotify(lpNewExtensionData, lpAdviseNotify);
if (FAILED(hr))
goto cleanup;
}
// Replace the old extension data property with the new one.
MAPIFREEBUFFER(lpAdviseObj->lpExtensionData);
lpAdviseObj->lpExtensionData = lpNewExtensionData;
lpNewExtensionData = NULL;
// Replace the old USN-Changed value with the new one.
lpAdviseObj->ulUSNChanged = ulNewUSNChanged;
}
cleanup:
MAPIFREEBUFFER(lpNewExtensionData);
// Unlock the object to allow access by other threads.
LeaveCriticalSection(&lpAdviseObj->csCriticalSection);
RETURN(hr);
}
//$--HrCfgDestroyAdviseObj------------------------------------------------------
// End monitoring of a MAPI session. This routine calls HrCfgUnadvise() for
// any extension data sections that are actively being monitored.
// -----------------------------------------------------------------------------
HRESULT HrCfgDestroyAdviseObj( // RETURNS: HRESULT
IN LPADVISEOBJ lpAdviseObj) // advise object to destroy
{
HRESULT hr = NOERROR;
HRESULT hrT = NOERROR;
BOOL fItWorked = TRUE;
DWORD dwStatus = 0;
DEBUGPUBLIC("HrCfgDestroyAdviseObj()\n");
hr = CHK_HrCfgDestroyAdviseObj(
lpAdviseObj);
if (FAILED(hr))
RETURN(hr);
if (lpAdviseObj)
{
// Signal the thread to stop and then release the signal event.
if (lpAdviseObj->hPollThreadStop)
{
fItWorked = SetEvent(lpAdviseObj->hPollThreadStop);
if (!fItWorked)
{
hr = HR_LOG(E_FAIL);
}
CLOSEHANDLE(lpAdviseObj->hPollThreadStop);
}
// Wait for thread to terminate and then release it's handle.
if (lpAdviseObj->hPollThread)
{
dwStatus = WaitForSingleObject(lpAdviseObj->hPollThread, INFINITE);
if (dwStatus != WAIT_OBJECT_0)
{
hr = HR_LOG(E_FAIL);
}
CLOSEHANDLE(lpAdviseObj->hPollThread);
}
// Free the notification structures.
while (lpAdviseObj->lpNotifyList)
{
hrT = HrDestroyAdviseNotify(&lpAdviseObj->lpNotifyList);
if (FAILED(hrT))
{
hr = hrT;
}
}
// Delete the critical section.
DeleteCriticalSection(&lpAdviseObj->csCriticalSection);
// Release the IMAPIProp interface.
ULRELEASE(lpAdviseObj->lpCurrentObject);
// Free the extension data property.
MAPIFREEBUFFER(lpAdviseObj->lpExtensionData);
// Free the advise object itself.
MAPIFREEBUFFER(lpAdviseObj);
}
RETURN(hr);
}
//$--HrCfgAdvise----------------------------------------------------------------
// Begin monitoring of an extension data section. When the extension data
// changes, the specified callback routine is called. Note: The callback
// routine will be called once from hrCfgAdvise() to set the initial extension
// data values.
// -----------------------------------------------------------------------------
HRESULT HrCfgAdvise( // RETURNS: HRESULT
IN LPADVISEOBJ lpAdviseObj, // advise object
IN LPSTR lpszSection, // name of extension data section
IN LPADVISECALLBACK lpfnCallback, // function to call on changes
IN LPVOID lpvUserContext) // user-defined context
{
HRESULT hr = NOERROR;
LPADVISENOTIFY lpNotify = NULL;
ULONG cchSection = 0;
ULONG cchActual = 0;
DEBUGPUBLIC("HrCfgAdvise()\n");
hr = CHK_HrCfgAdvise(
lpAdviseObj,
lpszSection,
lpfnCallback,
lpvUserContext);
if (FAILED(hr))
RETURN(hr);
// Allocate memory for the ADVISENOTIFY structure.
hr = MAPIAllocateBuffer(
sizeof(*lpNotify),
&lpNotify);
if (FAILED(hr))
goto cleanup;
ZeroMemory(lpNotify, sizeof(*lpNotify));
// Get the number of characters in the section name.
cchSection = lstrlen(lpszSection);
// Allocate memory for the LPSTR section name and copy the section
// name into it.
hr = MAPIAllocateMore(
cchSection + 1,
lpNotify,
&lpNotify->lpszOwnerTag);
if (FAILED(hr))
goto cleanup;
lstrcpy(lpNotify->lpszOwnerTag, lpszSection);
// Allocate memory for the UNICODE section name, and copy the section
// name into it, converting to UNICODE.
hr = MAPIAllocateMore(
(cchSection + 1) * sizeof(WCHAR),
lpNotify,
&lpNotify->lpwszOwnerTag);
if (FAILED(hr))
goto cleanup;
SetLastError(ERROR_SUCCESS);
cchActual = wsprintfW(lpNotify->lpwszOwnerTag, L"%hs", lpszSection);
if (GetLastError() != ERROR_SUCCESS || cchActual != cchSection)
{
hr = HR_LOG(E_FAIL);
goto cleanup;
}
// Fill in the structure members.
lpNotify->lpfnCallback = lpfnCallback;
lpNotify->lpvUserContext = lpvUserContext;
// Lock the object against access by other threads.
EnterCriticalSection(&lpAdviseObj->csCriticalSection);
// Cause the callback routine to be called for the first time.
lpNotify->fFirstTime = TRUE;
hr = HrUpdateAdviseNotify(lpAdviseObj->lpExtensionData, lpNotify);
// NOTE: Don't check the return value for failure until after
// we leave the critical section.
// Link the structure into the object's list.
if (SUCCEEDED(hr))
{
lpNotify->lpNext = lpAdviseObj->lpNotifyList;
lpAdviseObj->lpNotifyList = lpNotify;
}
// Unlock the object to allow access by other threads.
LeaveCriticalSection(&lpAdviseObj->csCriticalSection);
cleanup:
if (FAILED(hr))
{
MAPIFREEBUFFER(lpNotify);
}
RETURN(hr);
}
//$--HrCfgUnadvise--------------------------------------------------------------
// End monitoring of an extension data section.
// -----------------------------------------------------------------------------
HRESULT HrCfgUnadvise( // RETURNS: HRESULT
IN LPADVISEOBJ lpAdviseObj, // advise object
IN LPSTR lpszSection) // name of extension data section
{
HRESULT hr = NOERROR;
LPADVISENOTIFY * lppNotify = NULL;
BOOL fFound = FALSE;
DEBUGPUBLIC("HrCfgAdvise()\n");
hr = CHK_HrCfgUnadvise(
lpAdviseObj,
lpszSection);
if (FAILED(hr))
RETURN(hr);
// Lock the object against access by other threads.
EnterCriticalSection(&lpAdviseObj->csCriticalSection);
// Search the list of ADVISENOTIFY structures for the section name we want.
for (
lppNotify = &lpAdviseObj->lpNotifyList;
*lppNotify;
lppNotify = &((*lppNotify)->lpNext))
{
if (!lstrcmp(lpszSection, (*lppNotify)->lpszOwnerTag))
{
// Found it, so destroy it.
hr = HrDestroyAdviseNotify(lppNotify);
fFound = TRUE;
break;
}
}
if (!fFound)
{
hr = HR_LOG(E_FAIL);
}
// Unlock the object to allow access by other threads.
LeaveCriticalSection(&lpAdviseObj->csCriticalSection);
RETURN(hr);
}
//
// Local Helper Routines
//
//$--PollThread-----------------------------------------------------------------
// Function that is called as a seperate thread. It wakes up periodically
// and polls the extension data sections. If any have changed, it calls
// the routine registered for that section.
// -----------------------------------------------------------------------------
static DWORD WINAPI PollThread( // RETURNS: error code
IN LPVOID lpvThreadParam) // pointer to advise object
{
HRESULT hr = NOERROR;
LPADVISEOBJ lpAdviseObj = lpvThreadParam;
BOOL fDone = FALSE;
DWORD dwStatus = 0;
DEBUGPRIVATE("PollThread()\n");
hr = CHK_PollThread(
lpvThreadParam);
if (FAILED(hr))
RETURN(hr);
// Main loop: sleep, poll object.
while (!fDone)
{
// Sleep and wait for the timer.
dwStatus = WaitForSingleObject(
lpAdviseObj->hPollThreadStop,
lpAdviseObj->nPollInterval);
switch (dwStatus)
{
case WAIT_TIMEOUT:
// Poll the object.
hr = HrCfgPollAdviseObj(lpAdviseObj);
if (FAILED(hr))
goto cleanup;
break;
case WAIT_OBJECT_0:
// Thread terminate request.
fDone = TRUE;
break;
default:
// Some kind of error.
hr = HR_LOG(E_FAIL);
goto cleanup;
}
}
cleanup:
return(ERROR_SUCCESS);
}
//$--HrUpdateAdviseNotify-------------------------------------------------------
// Compare new extension data property with the old one for changes to the
// blob defined by the ADVISENOTIFY structure. If it has changed, then call
// the callback routine. In any case, point the ADVISENOTIFY structure at the
// new extension data blob, so that the old one may be freed.
// -----------------------------------------------------------------------------
static HRESULT HrUpdateAdviseNotify( // RETURNS: HRESULT
IN LPSPropValue lpNewData, // new extension data
IN OUT LPADVISENOTIFY lpNotify) // notification structure
{
HRESULT hr = NOERROR;
LPSBinary lpNewBlobArray = NULL;
ULONG cNewBlobArray = 0;
ULONG iBlob = 0;
LPBYTE lpbNewBlob = NULL;
ULONG cbNewBlob = 0;
LPSPropValue lpUnpackedProps = NULL;
ULONG cUnpackedProps = 0;
LPSTR lpszUnpackedName = NULL;
DEBUGPRIVATE("HrUpdateAdviseNotify()\n");
hr = CHK_HrUpdateAdviseNotify(
lpNewData,
lpNotify);
if (FAILED(hr))
RETURN(hr);
// Find the owner tag in the new extension data property array.
if (lpNewData)
{
lpNewBlobArray = lpNewData->Value.MVbin.lpbin;
cNewBlobArray = lpNewData->Value.MVbin.cValues;
for (iBlob = 0; iBlob < cNewBlobArray; iBlob++)
{
if (!wcscmp(
lpNotify->lpwszOwnerTag,
(LPWSTR) lpNewBlobArray[iBlob].lpb))
{
lpbNewBlob = lpNewBlobArray[iBlob].lpb;
cbNewBlob = lpNewBlobArray[iBlob].cb;
}
}
}
// If the blob has changed then unpack the properties and call the
// callback routine.
if (lpNotify->fFirstTime ||
lpNotify->cbBlob != cbNewBlob ||
(lpNotify->lpbBlob &&
lpbNewBlob &&
memcmp(lpNotify->lpbBlob, lpbNewBlob, cbNewBlob)))
{
// If the new blob exists then unpack the properties.
// Otherwise, use a NULL pointer for a non-existant blob.
if (lpbNewBlob)
{
hr = HrCfgUnpackData(
cbNewBlob,
lpbNewBlob,
&lpszUnpackedName,
&cUnpackedProps,
&lpUnpackedProps);
if (FAILED(hr))
goto cleanup;
}
// Call the callback routine.
hr = (*lpNotify->lpfnCallback)(
lpNotify->lpvUserContext,
lpNotify->lpwszOwnerTag,
cUnpackedProps,
lpUnpackedProps);
if (FAILED(hr))
goto cleanup;
// If the callback succeeded then zero out the unpacked property
// variables so we don't free them (that's the user's responsiblity).
cUnpackedProps = 0;
lpUnpackedProps = NULL;
// Callback has now been called at least once.
lpNotify->fFirstTime = FALSE;
}
// Replace the old blob with the new one in the ADVISENOTIFY structure.
lpNotify->lpbBlob = lpbNewBlob;
lpNotify->cbBlob = cbNewBlob;
cleanup:
MAPIFREEBUFFER(lpszUnpackedName);
if (FAILED(hr))
{
MAPIFREEBUFFER(lpUnpackedProps);
}
RETURN(hr);
}
//$--HrDestroyAdviseNotify------------------------------------------------------
// Destroy an ADVISENOTIFY structure and remove it from the linked list.
// This routine takes as its argument the address of a pointer to the
// structure to be destroyed and returns with that pointer pointing to
// the next structure in the linked list.
// -----------------------------------------------------------------------------
static HRESULT HrDestroyAdviseNotify( // RETURNS: HRESULT
IN OUT LPADVISENOTIFY * lppNotify) // address of ptr to ADVISENOTIFY
{
HRESULT hr = NOERROR;
LPADVISENOTIFY lpNotify = NULL;
DEBUGPRIVATE("HrDestroyAdviseNotify()\n");
hr = CHK_HrDestroyAdviseNotify(
lppNotify);
if (FAILED(hr))
RETURN(hr);
// Save pointer to the structure.
lpNotify = *lppNotify;
// Take structure out of list, by making whatever pointed to it point
// to the next thing on the list.
*lppNotify = lpNotify->lpNext;
// Free the structure.
MAPIFREEBUFFER(lpNotify);
RETURN(hr);
}