MSPMS.C

/* 
* M S P M S . C
*
* Code for the MAPI Sample Store Provider implementation of the
* IMsgStore object.
*
* Copyright 1992-1995 Microsoft Corporation. All Rights Reserved.
*/

#include "msp.h"
#include <stdarg.h>

#define MS_ValidateParameters(pobj, intf, method, arglist) \
OBJ_ValidateParameters(pobj, intf, method, sizeof(IMS), &vtblIMS, arglist)

static HRESULT HrGetSequenceNum(PEID peid, ULONG *pulSequenceNum);
static HRESULT HrGetSMSStandardNotifKey(PIMS pims, PEID peid,
LPNOTIFKEY * lppKey);

/* Dispatch table for IMsgStore objects */
IMS_Vtbl vtblIMS =
{
(IMS_QueryInterface_METHOD *) OBJ_QueryInterface,
(IMS_AddRef_METHOD *) OBJ_AddRef,
(IMS_Release_METHOD *) OBJ_Release,
IMS_GetLastError,
IMS_SaveChanges,
IMS_GetProps,
IMS_GetPropList,
IMS_OpenProperty,
IMS_SetProps,
IMS_DeleteProps,
IMS_CopyTo,
IMS_CopyProps,
IMS_GetNamesFromIDs,
IMS_GetIDsFromNames,

IMS_Advise,
IMS_Unadvise,
IMS_CompareEntryIDs,
IMS_OpenEntry,
IMS_SetReceiveFolder,
IMS_GetReceiveFolder,
IMS_GetReceiveFolderTable,
IMS_StoreLogoff,
IMS_AbortSubmit,

IMS_GetOutgoingQueue,
IMS_SetLockState,
IMS_FinishedMsg,
IMS_NotifyNewMail,
};

/* definitions for outgoing queues */
CALLERRELEASE OutgoingViewRelease;

/*
* Object methods
*/

/*
* IMS_GetLastError [also called by IMSLogon, IMAPIFolder, IMessage, and IAttach]
*
* Purpose:
* Returns a localized text error message associated with the
* last error which occurred on a specific object (in
* actuality, the SCODE used is associated with the HRESULT
* passed in, and it is not checked that this HRESULT matches
* that of the last error on this object -- providing a
* different HRESULT will very likely return an inconsistent
* error message, though).
*
* Arguments:
* pobj Pointer to the object.
* hError HRESULT containing the error code returned
* by the last failed call on this object.
* ulFlags MAPI_UNICODE, string8 is default
* pulLLErr location to place the low level error code
* (may be NULL)
* pszMessage Location in which to return an address to
* an allocated buffer containing the
* localized text error message.
* pszComponent Location in which to return an address to
* an allocated buffer containing the
* name of the component producing the error.
* lpulContext Location in which to return a context number
* for finding help within the Component's help file.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NOT_ENOUGH_MEMORY Unable to allocate memory for
* the return parameter.
* MAPI_E_INVALID_PARAMETER hError contains an unknown
* SCODE.
*/
STDMETHODIMP IMS_GetLastError(PIMS pobj, HRESULT hError, ULONG ulFlags,
LPMAPIERROR * lppMapiError)
{
HRESULT hr = hrSuccess;
SCODE sc;
PIMS pims;
LPTSTR pszMessage = NULL;

#ifdef VALIDATE
if (IsBadWritePtr(pobj, sizeof(OBJ))
|| (pobj->lpVtbl != (IMS_Vtbl *) &vtblIMS
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblMSL
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblIFLD
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblIMSG
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblIATCH))
return ResultFromScode(MAPI_E_INVALID_PARAMETER);

Validate_IMAPIProp_GetLastError(pobj, hError, ulFlags, lppMapiError);

if (ulFlags & MAPI_UNICODE)
return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
#endif

OBJ_EnterCriticalSection((POBJ) pobj);

pims = pobj->pims;

/* //$ Add param checking and correctly return Component & Context */
*lppMapiError = NULL;

sc = LMAlloc( &pims->lmr, sizeof( MAPIERROR ), lppMapiError );
if ( FAILED( sc ) )
{
hr = ResultFromScode( sc );
goto ret;
}

memset( *lppMapiError, 0, sizeof( MAPIERROR ) );

(*lppMapiError)->ulVersion = MAPI_ERROR_VERSION;

hr = MapScodeSz(GetScode(hError), pims, &pszMessage);
if ( HR_FAILED( hr ) )
goto ret;

sc = LMAllocMore( &pims->lmr, Cbtszsize( pszMessage ), *lppMapiError,
&(*lppMapiError)->lpszError );

if ( FAILED( sc ) )
{
hr = ResultFromScode( sc );
goto ret;
}

lstrcpy( (*lppMapiError)->lpszError, pszMessage );

ret:

if ( hr )
{
LMFree( &pims->lmr, *lppMapiError );
*lppMapiError = NULL;
}

LMFree( &pims->lmr, pszMessage );

OBJ_LeaveCriticalSection(pobj);

DebugTraceResult(IMS_GetLastError, hr);
return HrCheckHr(hr, IUnknown_GetLastError);
}

/*
* IMS_SaveChanges [Also used by IMAPIFolder]
*
* Purpose:
* Saves changes made to the message store object properties
* (does not propagate to sub-objects). Because changes to
* message store object properties show up immediately,
* however, this call does nothing (but returns success).
*
* Arguments:
* pims Pointer to the object.
* ulFlags Flags. The following are defined:
* KEEP_OPEN_READONLY Do not invalidate the
* object, make it read-only.
* KEEP_OPEN_READWRITE Don't invalidate the
* object, keep it open
* read/write.
* FORCE_SAVE Overwrite any changes made by
* others since store was opened
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* None.
*/
STDMETHODIMP IMS_SaveChanges(PIMS pims, ULONG ulFlags)
{
#ifdef VALIDATE
if ( IsBadWritePtr(pims, sizeof(OBJ))
|| ( pims->lpVtbl != (IMS_Vtbl *) &vtblIMS
&& pims->lpVtbl != (IMS_Vtbl *) &vtblIFLD))
return ResultFromScode(MAPI_E_INVALID_PARAMETER);

Validate_IMAPIProp_SaveChanges(pims, ulFlags);
#endif

return hrSuccess;
}

/*
* IMS_GetProps
*
* Purpose:
* Returns to the caller the value(s) of one or more
* properties existent on an IMS object. The order of the
* properties in the returned ppval structure exactly
* matches the order in which the properties were requested in
* ptaga. The caller must free the returned
* structure by calling MAPIFreeBuffer(*ppval), but
* only if the function returns zero or the error
* MAPI_W_ERRORS_RETURNED. Uses the IMessage on IStorage
* property interface implementation.
*
* Arguments:
* pims Pointer to the object.
* ptaga Pointer to a counted array of property tags
* ("names") that identify the values to be
* returned.
* ulFlags UNICODE / String8
* pcval Location in which to return the count of
* elements in *ppval.
* ppval Location in which to return an allocated
* array of property values (the caller frees
* by calling MAPIFreeBuffer).
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* If the call succeeded overall but access to one or more
* properties failed, the function returns the warning
* MAPI_W_ERRORS_RETURNED. The calling application should
* then check the Property Tag of each of the returned
* properties to determine which ones failed. Those that fail
* have their Property Type set to PT_ERROR and their value (a
* ULONG) indicates which error occurred.
*
* MAPI_E_NO_ACCESS The caller does not have access
* to the requested properties.
* MAPI_W_ERRORS_RETURNED See above.
* MAPI_E_CALL_FAILED The mechanism for making the
* call to the service provider
* failed.
*/
STDMETHODIMP IMS_GetProps(PIMS pims, LPSPropTagArray ptaga, ULONG ulFlags,
ULONG *pcval, LPSPropValue *ppval)
{
HRESULT hr = hrSuccess;
LPMESSAGE lpmsg = NULL;

MS_ValidateParameters(
pims,
IMAPIProp,
GetProps,
(pims,
ptaga,
ulFlags,
pcval,
ppval));

#ifdef VALIDATE
if (ulFlags & MAPI_UNICODE)
return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
#endif

IMS_EnterCriticalSection(pims);

/* If input parameters are okay, make GetProps call on lpmsgProps. */

*pcval = 0L;
*ppval = NULL;

hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
pims->psup, FALSE, &lpmsg);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->GetProps(lpmsg, ptaga, ulFlags, pcval, ppval);

{if(HR_SUCCEEDED(hr))
{
LPSPropValue pvalStoreSupMask = PpropFindProp(*ppval, *pcval,
PROP_TAG(PT_UNSPECIFIED, PROP_ID(PR_STORE_SUPPORT_MASK)));
if(pvalStoreSupMask)
{
pvalStoreSupMask->ulPropTag = PR_STORE_SUPPORT_MASK;
pvalStoreSupMask->Value.l = SMS_SUPPORTMASK;

/* fix up hr */
if(ptaga->cValues == 1)
hr = hrSuccess;
}
}
}
/* Wrap the store entryids. Note that this function takes as an */
/* argument the HRESULT from the previous GetProps call. */
/* We aren't ignoring the error. */

hr = HrWrap_GetProps(hr, pims, 0, NULL, pcval, ppval, TRUE,
(ptaga != NULL), (POBJ)pims);

exit:
UlRelease(lpmsg);

IMS_LeaveCriticalSection(pims);

#ifdef DEBUG
if (GetScode(hr) != MAPI_W_ERRORS_RETURNED)
DebugTraceResult(IMS_GetProps, hr);
#endif

return HrCheckHr(hr, IMAPIProp_GetProps);
}

/*
* IMS_GetPropList
*
* Purpose:
* Returns a list of all the properties currently accessible.
* Uses the IMessage on IStorage property implementation.
*
* Arguments:
* pims Pointer to the object.
* ulFlags UNICODE / String8
* pptaga Location in which to return a pointer
* to a counted array of property tags.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NO_ACCESS The caller does not have access
* to the requested properties.
* MAPI_E_CALL_FAILED The mechanism for making the
* call to the service provider
* failed.
*/
STDMETHODIMP IMS_GetPropList(PIMS pims, ULONG ulFlags, LPSPropTagArray * pptaga)
{
HRESULT hr = hrSuccess;
LPMESSAGE lpmsg = NULL;

MS_ValidateParameters(
pims,
IMAPIProp,
GetPropList,
(pims,
ulFlags,
pptaga));

#ifdef VALIDATE
if (ulFlags & MAPI_UNICODE)
return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
#endif

IMS_EnterCriticalSection(pims);

/* If input parameters are okay, make GetPropList call on lpmsgProps. */

hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
pims->psup, FALSE, &lpmsg);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->GetPropList(lpmsg, ulFlags, pptaga);
/* if ( hr ) fall through to exit */

exit:
UlRelease(lpmsg);

IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_GetPropList, hr);
return HrCheckHr(hr, IMAPIProp_GetPropList);
}

/*
* IMS_OpenProperty
*
* Purpose:
* Open a requested interface on a property for further
* access. Commonly used for stream access to a large binary
* or text property. This is the only way to access a
* property of type PT_OBJECT, and may be used on other
* properties depending on the implementation. Uses the
* IMessage on IStorage property implementation, but since the
* transaction model for IMsgOnIStg does not match that for
* IMsgStore, we fail this call. (We could do a lot of work
* to wrap the interface that IMsgOnIStg returns to us before
* returning it to the client, but we don't.)
*
* Arguments:
* pims Pointer to the object.
* ulPropTag Property tag for the desired property. Only
* the ID bits of the tag are used; the type bits
* are ignored.
* lpiid Pointer to the GUID identifying which interface
* is desired.
* lppUnk Location in which to return a pointer to the
* newly created interface pointer.
*
* Returns:
* HRESULT
*
* Errors:
* MAPI_E_INVALID_PARAMETER
* MAPI_E_NO_SUPPORT The requested interface is not
* available on the given property.
*/
STDMETHODIMP IMS_OpenProperty(PIMS pims, ULONG ulPropTag, LPCIID lpiid,
ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN * lppUnk)
{
SCODE sc;

MS_ValidateParameters(
pims,
IMAPIProp,
OpenProperty,
(pims,
ulPropTag,
lpiid,
ulInterfaceOptions,
ulFlags,
lppUnk));

sc = MAPI_E_NO_SUPPORT;

DebugTraceSc(IFLD_OpenProperty, sc);
return ResultFromScode(sc);
}

/*
* IMS_SetProps
*
* Purpose:
* Sets the value of one or more properties. This call passes
* a number of Property Value structures. The Property Tag in
* each indicates which property is having its values set and
* the value indicates what should be stored. The caller must
* free the returned property problem structure by calling
* MAPIFreeBuffer(*lppProblems), but only if the call
* succeeded overall. Uses the IMessage on IStorage property
* implementation.
*
* Arguments:
* pims Pointer to the object.
* cValues Number of values in lpPropArray.
* lpPropArray Pointer to a Property Value array.
* lppProblems Location in which to return a pointer to a
* counted array of property problem
* structures.
*
* Returns:
* HRESULT. If the call succeeds overall, a zero is returned.
* If there are problems with setting some or all of the
* selected values, and a non-NULL is passed for lppProblems,
* then a SPropProblemArray structure is returned with details
* about each problem. The value returned in lppProblems is
* only valid if zero is returned in the HRESULT. If an error
* occurs on the call such that a non-zero value is returned
* for the HRESULT then the contents of *lppProblems are
* undefined. In particular, do not use or free the structure
* if an error occurs on the call.
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NO_ACCESS The caller does not have access
* to the requested properties.
* MAPI_E_CALL_FAILED A general problem affecting
* access to all of the object's
* properties occurred.
* MAPI_E_CALL_FAILED The mechanism for making the
* call to the service provider
* failed.
*/
STDMETHODIMP IMS_SetProps(PIMS pims, ULONG cValues, LPSPropValue lpPropArray,
LPSPropProblemArray * lppProblems)
{
HRESULT hr = hrSuccess;
LPMESSAGE lpmsg = NULL;

MS_ValidateParameters(
pims,
IMAPIProp,
SetProps,
(pims,
cValues,
lpPropArray,
lppProblems));

IMS_EnterCriticalSection(pims);

if (!OBJ_TestFlag(pims, OBJF_MODIFY))
{
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}

/* If input parameters are okay, make SetProps call on lpmsgProps. */

hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
pims->psup, TRUE, &lpmsg);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->SetProps(lpmsg, cValues, lpPropArray, lppProblems);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);
/* if ( hr ), fall through to exit */

exit:
UlRelease(lpmsg);

IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_SetProps, hr);
return HrCheckHr(hr, IMAPIProp_SetProps);
}

/*
* IMS_DeleteProps
*
* Purpose:
* Deletes the list of properties given in ptaga.
* The caller must free the returned property problem
* structure by calling MAPIFreeBuffer(*pprba), but only
* if the call succeeded overall. Uses the IMessage on
* IStorage property implementation.
*
* Arguments:
* pims Pointer to the object.
* ptaga Pointer to an array of Property Tags
* identifying the properties to delete.
* pprba Location in which to return a pointer to a
* counted array of property problem
* structures.
*
* Returns:
* HRESULT. If the call succeeds overall, a zero is returned.
* If there are problems with deleting some or all of the
* selected values, and a non-NULL is passed for pprba,
* then a SPropProblemArray structure is returned with details
* about each problem. The value returned in pprba is
* only valid if zero is returned in the HRESULT. If an error
* occurs on the call such that a non-zero value is returned
* for the HRESULT then the contents of *pprba are
* undefined. In particular, do not use or free the structure
* if an error occurs on the call.
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NO_ACCESS The caller does not have access
* to the requested properties.
* MAPI_E_CALL_FAILED A general problem affecting
* access to all of the object's
* properties occurred.
* MAPI_E_CALL_FAILED The mechanism for making the
* call to the service provider
* failed.
*/
STDMETHODIMP IMS_DeleteProps(PIMS pims, LPSPropTagArray ptaga,
LPSPropProblemArray *pprba)
{
HRESULT hr = hrSuccess;
LPMESSAGE lpmsg = NULL;

MS_ValidateParameters(
pims,
IMAPIProp,
DeleteProps,
(pims,
ptaga,
pprba));

IMS_EnterCriticalSection(pims);

if (!OBJ_TestFlag(pims, OBJF_MODIFY))
{
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}

/* If input parameters are okay, make DeleteProps call on lpmsg. */

hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
pims->psup, TRUE, &lpmsg);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->DeleteProps(lpmsg, ptaga, pprba);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);
/* if ( hr ), fall through to exit */

exit:
UlRelease(lpmsg);

IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_DeleteProps, hr);
return HrCheckHr(hr, IMAPIProp_DeleteProps);
}

/*
* IMS_CopyTo
*
* Purpose:
* Copies the contents of the current object to a destination
* object. The entire contents, including contained objects,
* are copied, or optionally the caller can provide a list of
* properties that are not to be copied. Previous information
* in the destination object which is not overwritten by
* copied data is neither deleted nor modified. It is not a
* requirement in MAPI 1.0, however, that CopyTo semantics
* understood by the message store object or folder objects,
* and so this method always returns MAPI_E_NO_SUPPORT.
*
* Arguments:
* pims Pointer to the source object.
* ciidExclude Count of the excluded interfaces in
* rgiidExclude.
* rgiidExclude Array of interface IDs specifying
* interfaces not to be attempted in trying to
* copy supplemental information to the
* destination object.
* ptagaExcl Counted array of property tags of
* properties that are not to be copied to the
* destination object. NULL indicates all
* properties are to be copied.
* ulUIParam Handle of parent window cast to ULONG.
* lpProgress Callback for doing progress UI.
* piidDst Interface ID of the interface of lpDestObj,
* the destination object.
* lpDestObj Pointer to the open destination object.
* ulFlags Flags. Defined as follows:
* MAPI_MOVE Indicates a move operation.
* The default is to copy.
* MAPI_NOREPLACE Indicates that existing
* properties should not be
* overridden. The default is
* to overwrite existing
* properties.
* MAPI_DIALOG Display a progress dialog
* as the operation proceeds.
* pprba Pointer to a variable that is filled in
* with a pointer to a set of property
* problems. If NULL, no problem set is
* returned on an error.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NO_SUPPORT This method is not supported by
* this type of object.
*/
STDMETHODIMP IMS_CopyTo(PIMS pims, ULONG ciidExcl, LPCIID rgiidExcl,
LPSPropTagArray ptagaExcl, ULONG ulUIParam, LPMAPIPROGRESS
lpProgress, LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlags,
LPSPropProblemArray *pprba)
{
SCODE sc;

MS_ValidateParameters(
pims,
IMAPIProp,
CopyTo,
(pims,
ciidExcl,
rgiidExcl,
ptagaExcl,
ulUIParam,
lpProgress,
piidDst,
lpDestObj,
ulFlags,
pprba));

sc = MAPI_E_NO_SUPPORT;

DebugTraceSc(IMS_CopyTo, sc);
return ResultFromScode(sc);
}

/*
* IMS_CopyProps
*
* Purpose:
* Copies the specified properties of the current object to a destination
* object.
*
* Arguments:
* pims Pointer to the source object.
* ptagaIncl Counted array of property tags of
* properties that are to be copied to the
* destination object.
* ulUIParam Handle of parent window cast to ULONG.
* lpProgress Callback for doing progress UI.
* piidDst Interface ID of the interface of lpDestObj,
* the destination object.
* lpDestObj Pointer to the open destination object.
* ulFlags Flags. Defined as follows:
* MAPI_MOVE Indicates a move operation.
* The default is to copy.
* MAPI_NOREPLACE Indicates that existing
* properties should not be
* overridden. The default is
* to overwrite existing
* properties.
* MAPI_DIALOG Display a progress dialog
* as the operation proceeds.
* MAPI_DECLINE_OK
* pprba Pointer to a variable that is filled in
* with a pointer to a set of property
* problems. If NULL, no problem set is
* returned on an error.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NO_SUPPORT This method is not supported by
* this type of object.
*/
STDMETHODIMP IMS_CopyProps(PIMS pims,
LPSPropTagArray ptagaIncl, ULONG ulUIParam, LPMAPIPROGRESS
lpProgress, LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlags,
LPSPropProblemArray *pprba)
{
SCODE sc;

MS_ValidateParameters(
pims,
IMAPIProp,
CopyProps,
(pims,
ptagaIncl,
ulUIParam,
lpProgress,
piidDst,
lpDestObj,
ulFlags,
pprba));

sc = MAPI_E_NO_SUPPORT;

DebugTraceSc(IMS_CopyProps, sc);
return ResultFromScode(sc);
}

/*
* IMS_GetNamesFromIDs [also used by IFLD, IMSG, IATCH]
*
* Purpose:
*
* Arguments:
*
* Returns:
* HRESULT
*
* Side effects:
*
* Errors:
* MAPI_E_NO_SUPPORT This method is not yet supported in the sample ms.
*/
STDMETHODIMP IMS_GetNamesFromIDs(PIMS pobj, LPSPropTagArray * pptaga,
LPGUID lpguid, ULONG ulFlags, ULONG * pcNames, LPMAPINAMEID ** pppNames)
{
HRESULT hr = hrSuccess;
LPMAPIPROP pmp = NULL;
BOOL fReleasePMP = FALSE;

/* Check input parameters. */

#ifdef VALIDATE
if ( IsBadWritePtr(pobj, sizeof(OBJ))
|| ( pobj->lpVtbl != (IMS_Vtbl *) &vtblIMS
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblIFLD
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblIMSG
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblIATCH))
return ResultFromScode(MAPI_E_INVALID_PARAMETER);

Validate_IMAPIProp_GetNamesFromIDs(
pobj,
pptaga,
lpguid,
ulFlags,
pcNames,
pppNames);
#endif

OBJ_EnterCriticalSection(pobj);

switch(pobj->wType)
{
case OT_MSGSTORE:
{
PIMS pims = pobj->pims;

hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps,
&pims->lmr, pims->psup, FALSE, (LPMESSAGE *) &pmp);
if (hr != hrSuccess)
goto exit;

fReleasePMP = TRUE;
break;
}

case OT_FOLDER:
{
PIFLD pifld = (PIFLD) pobj;

hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, FALSE,
(LPMESSAGE *) &pmp);
if (hr != hrSuccess)
goto exit;

fReleasePMP = TRUE;
break;
}

case OT_MESSAGE:
pmp = (LPMAPIPROP) ((PIMSG) pobj)->lpmsg;
break;

case OT_ATTACH:
pmp = (LPMAPIPROP) ((PIATCH) pobj)->lpattach;
break;

default:
TrapSz1("Invalid Object Type %08lX found", pobj->wType);
hr = ResultFromScode(MAPI_E_CALL_FAILED);
break;
}

hr = pmp->lpVtbl->GetNamesFromIDs(pmp, pptaga, lpguid, ulFlags, pcNames,
pppNames);

exit:
if (fReleasePMP)
UlRelease(pmp);

OBJ_LeaveCriticalSection(pobj);

DebugTraceResult(IMS_GetNamesFromIDs, hr);
return hr;
}

/*
* IMS_GetIDsFromNames [also used by IFLD, IMSG, IATCH]
*
* Purpose:
*
* Arguments:
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
*/
STDMETHODIMP IMS_GetIDsFromNames(PIMS pobj, ULONG cNames,
LPMAPINAMEID * ppNames, ULONG ulFlags, LPSPropTagArray * pptaga)
{
HRESULT hr = hrSuccess;
LPMAPIPROP pmp = NULL;
BOOL fSaveReleasePMP = FALSE;
BOOL fModifyAccess;

/* Check input parameters. */
#ifdef VALIDATE
if ( IsBadWritePtr(pobj, sizeof(OBJ))

|| (    pobj->lpVtbl != (IMS_Vtbl *) &vtblIMS 
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblIFLD
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblIMSG
&& pobj->lpVtbl != (IMS_Vtbl *) &vtblIATCH))
return ResultFromScode(MAPI_E_INVALID_PARAMETER);

Validate_IMAPIProp_GetIDsFromNames(pobj, cNames, ppNames, ulFlags, pptaga);
#endif

OBJ_EnterCriticalSection(pobj);

fModifyAccess = !!(ulFlags & MAPI_CREATE);

// Check for correct access mode
if ( fModifyAccess
&& !OBJ_TestFlag(pobj, OBJF_MODIFY))
{
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}

switch(pobj->wType)
{
case OT_MSGSTORE:
{
PIMS pims = pobj->pims;

hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps,
&pims->lmr, pims->psup, fModifyAccess, (LPMESSAGE *) &pmp);
if (hr != hrSuccess)
goto exit;

fSaveReleasePMP = TRUE;
break;
}

case OT_FOLDER:
{
PIFLD pifld = (PIFLD) pobj;

hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims,
fModifyAccess, (LPMESSAGE *) &pmp);
if (hr != hrSuccess)
goto exit;

fSaveReleasePMP = TRUE;
break;
}

case OT_MESSAGE:
pmp = (LPMAPIPROP) ((PIMSG) pobj)->lpmsg;
break;

case OT_ATTACH:
pmp = (LPMAPIPROP) ((PIATCH) pobj)->lpattach;
break;

default:
TrapSz1("Invalid Object Type %08lX found", pobj->wType);
hr = ResultFromScode(MAPI_E_CALL_FAILED);
break;
}

// CAN RETURN WARNINGS!!! Should still continue even when a warning
// is returned.

hr = pmp->lpVtbl->GetIDsFromNames(pmp, cNames, ppNames, ulFlags, pptaga);
if (HR_FAILED(hr))
goto exit;

if (fSaveReleasePMP && fModifyAccess)
{
HRESULT hrT;

hrT = pmp->lpVtbl->SaveChanges(pmp, 0);
if (HR_FAILED(hrT))
{
LMFree(&pobj->lmr, *pptaga);
*pptaga = NULL;
hr = hrT;
}
}

exit:
if (fSaveReleasePMP)
UlRelease(pmp);

OBJ_LeaveCriticalSection(pobj);

#ifdef DEBUG
if (GetScode(hr) != MAPI_W_ERRORS_RETURNED)
DebugTraceResult(IMS_GetIDsFromNames, hr);
#endif

return hr;
}

/*
* IMS_Advise [Also used by IMSLogon]
*
* Purpose:
* Register a client's interest in a set of events that could
* occur to an object in this store. The client's particular
* interest is expressed in a ulEventMask, and he is told of
* changes through an Advise object which he gives us.
* The Sample Store uses the MAPI
* Notification Engine to handle notifications, so the
* Advise call is translated into a notification
* subscription with MAPI. When events occur in the store
* which would cause interested clients to be notified, the
* store calls back to MAPI to request that the occurrence of
* an event be broadcast.
*
* Arguments:
* pims Pointer to the message store object.
* cbEntryID Size of lpEntryID.
* lpEntryID Pointer to the ID for the object for
* which interest is being registered.
* if NULL then registration is for all changes.
* ulEventMask Indicates the events of interest and
* how to see them.
* lpAdviseSink Pointer to client's Advise Sink object.
* lpulConnection Pointer to a variable in which the
* client gets a cookie for cancelling
* the notifications.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_INVALID_ENTRYID The EntryID is not recognized
* as belonging to this store.
* MAPI_E_NO_SUPPORT The implementation does not
* support notification on this
* object.
* MAPI_E_NOT_ENOUGH_MEMORY Could not allocate space for a
* needed buffer.
*/
STDMETHODIMP IMS_Advise(PIMS pobj, ULONG cbEntryID, LPENTRYID lpEntryID,
ULONG ulEventMask, LPMAPIADVISESINK lpAdviseSink,
ULONG *lpulConnection)
{
HRESULT hr = hrSuccess;
LPNOTIFKEY lpnotifkey = NULL;
PIMS pims;
PEID peid;

/* Check input parameters. */

#ifdef VALIDATE
if ( IsBadWritePtr(pobj, sizeof(OBJ))
|| ( pobj->lpVtbl != (IMS_Vtbl *)&vtblIMS
&& pobj->lpVtbl != (IMS_Vtbl *)&vtblMSL))
return ResultFromScode(MAPI_E_INVALID_PARAMETER);

Validate_IMsgStore_Advise(
pobj,
cbEntryID,
lpEntryID,
ulEventMask,
lpAdviseSink,
lpulConnection);
#endif

/* If this is an EntryID for which we support notifications, call the */
/* MAPI registration function and return the notification object. */

OBJ_EnterCriticalSection(pobj);

pims = pobj->pims;
peid = (PEID) lpEntryID;
*lpulConnection = 0;

if (cbEntryID && FIsInvalidEID(cbEntryID, peid, pims))
{
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

if (cbEntryID && !FIsFolder(peid) && !FIsMessage(peid))
{
hr = ResultFromScode(MAPI_E_NO_SUPPORT);
goto exit;
}

if (cbEntryID == 0)
peid = NULL;

hr = HrGetSMSStandardNotifKey(pims, peid, &lpnotifkey);
if (hr != hrSuccess)
goto exit;

if (pims->psup)
{
hr = pims->psup->lpVtbl->Subscribe(pims->psup,
lpnotifkey, ulEventMask, 0L, lpAdviseSink, lpulConnection);
if (hr != hrSuccess)
goto exit;
}
else
{
hr = ResultFromScode(MAPI_E_CALL_FAILED);
/* fall through to exit */
}

exit:
OBJ_LeaveCriticalSection(pobj);

FreeNull(lpnotifkey);

DebugTraceResult(IMS_Advise, hr);
return HrCheckHr(hr, IMsgStore_Advise);
}

/*
* IMS_Unadvise [Also used by IMSLogon]
*
* Purpose:
* Deregister a previous notification.
*
* Arguments:
* pims Pointer to the message store object.
* ulConnection Cookie given out at Advise time.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
*/
STDMETHODIMP IMS_Unadvise(PIMS pobj, ULONG ulConnection)
{
HRESULT hr = hrSuccess;
LPMAPISUP psup;

/* Check input parameters. */

#ifdef VALIDATE
if ( IsBadWritePtr(pobj, sizeof(OBJ))
|| ( pobj->lpVtbl != (IMS_Vtbl *)&vtblIMS
&& pobj->lpVtbl != (IMS_Vtbl *)&vtblMSL))
return ResultFromScode(MAPI_E_INVALID_PARAMETER);

Validate_IMsgStore_Unadvise(pobj, ulConnection);
#endif

OBJ_EnterCriticalSection(pobj);

psup = pobj->pims->psup;

if (psup)
hr = psup->lpVtbl->Unsubscribe(psup, ulConnection);
else
hr = ResultFromScode(MAPI_E_CALL_FAILED);

OBJ_LeaveCriticalSection(pobj);

DebugTraceResult(IMS_Unadvise, hr);
return HrCheckHr(hr, IMsgStore_Unadvise);
}

/*
* IMS_CompareEntryIDs
*
* Purpose:
* Compares two EntryIDs to determine if they refer to the
* same object. This is useful because, in general, an object
* may have more than one valid EntryID. For the Sample
* Store, however, an object only has one valid EntryID at any
* time and so this function reduces to a check for binary
* equality of the EntryIDs.
*
* Arguments:
* pims Pointer to the Message Store Object.
* cbEntryID1 Size of first EntryID.
* lpEntryID1 Pointer to the first EntryID.
* cbEntryID2 Size of second EntryID.
* lpEntryID2 Pointer to the second EntryID.
* ulFlags Flags. Reserved. Must be zero.
* lpulResult Pointer to a variable in which the result of
* the comparison (TRUE or FALSE) is placed.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_INVALID_ENTRYID An EntryID is not recognized as
* belonging to this store.
*/
STDMETHODIMP IMS_CompareEntryIDs(PIMS pobj, ULONG cbEntryID1,
LPENTRYID lpEntryID1, ULONG cbEntryID2, LPENTRYID lpEntryID2, ULONG ulFlags,
ULONG *lpulResult)
{
HRESULT hr = hrSuccess;
PEID peid1;
PEID peid2;
PIMS pims;
BOOL fEID1IsRoot = FALSE;
BOOL fEID2IsRoot = FALSE;

/* Check input parameters. */

#ifdef VALIDATE
if ( IsBadWritePtr(pobj, sizeof(OBJ))
|| ( pobj->lpVtbl != (IMS_Vtbl *)&vtblIMS
&& pobj->lpVtbl != (IMS_Vtbl *)&vtblMSL))
return ResultFromScode(MAPI_E_INVALID_PARAMETER);

Validate_IMsgStore_CompareEntryIDs(
pobj,
cbEntryID1,
lpEntryID1,
cbEntryID2,
lpEntryID2,
ulFlags,
lpulResult);
#endif

OBJ_EnterCriticalSection(pobj);

/* Do a binary comparison of the EIDs, if they're the same size AND */
/* if we recognize one of them as belonging to this store. */

peid1 = (PEID) lpEntryID1;
peid2 = (PEID) lpEntryID2;
pims = pobj->pims;

if ((cbEntryID1 && FIsInvalidEID(cbEntryID1, peid1, pims))
|| (cbEntryID2 && FIsInvalidEID(cbEntryID2, peid2, pims)))
{
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

if (cbEntryID1 == 0 || FIsRoot(peid1))
fEID1IsRoot = TRUE;

if (cbEntryID2 == 0 || FIsRoot(peid2))
fEID2IsRoot = TRUE;

*lpulResult = FALSE;

if (fEID1IsRoot == fEID2IsRoot && fEID1IsRoot == TRUE)
*lpulResult = TRUE;
else if (cbEntryID1 == cbEntryID2
&& memcmp(peid1, peid2, offsetof(EID, szPath)) == 0
&& peid1->bVersion == SMPMS_VERSION
&& lstrcmpi(peid1->szPath, peid2->szPath) == 0)
*lpulResult = TRUE;

exit:
OBJ_LeaveCriticalSection(pobj);

DebugTraceResult(IMS_CompareEntryIDs, hr);
return hr;
}

/*
* IMS_OpenEntry [Also used by IMSLogon and IMAPIFolder]
*
* Purpose:
* Opens an object in this message store.
*
* Arguments:
* pobj Message store on which this function was
* called.
* cbEntryID Size of lpEntryID.
* lpEntryID EntryID of object to open.
* piid IID of interface requested for the
* newly-opened object. NULL or IID_IMAPIProp
* means to open the object using the standard
* MAPI 1.0 interface for the object.
* IID_IUnknown means to open it using
* the easiest interface you can open.
* ulFlags Flags. The following are defined:
* MAPI_MODIFY Write access desired.
* MAPI_DEFERRED_ERRORS Delayed "open" errors OKAY.
* MAPI_BEST_ACCESS Open for writing if possible,
* otherwise, open for reading.
* lpulObjType Address in which to place the type of the
* opened object.
* lppUnk Address in which to place a pointer to the
* opened object.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NOT_ENOUGH_MEMORY Could not allocate memory for
* the to-be-opened object.
* MAPI_E_INVALID_ENTRYID This message store does not
* recognize this EntryID.
* MAPI_E_UNKNOWN_FLAGS
* MAPI_E_INVALID_PARAMETER
*/
STDMETHODIMP IMS_OpenEntry(PIMS pobj, ULONG cbEntryID, LPENTRYID lpEntryID,
LPCIID piid, ULONG ulFlags, ULONG *lpulObjType, LPUNKNOWN *lppUnk)
{
HRESULT hr = hrSuccess;
PIMSG pimsg = NULL;
PIFLD pifld = NULL;
PEID peidParent = NULL;
BOOL fEIDIsRoot = FALSE;
BOOL fModify;
BOOL fBestAcc;
PIMS pims;
PEID peid;

/* Check input parameters. */

#ifdef VALIDATE
if ( IsBadWritePtr(pobj, sizeof(OBJ))
|| ( pobj->lpVtbl != (IMS_Vtbl *)&vtblIFLD
&& pobj->lpVtbl != (IMS_Vtbl *)&vtblIMS
&& pobj->lpVtbl != (IMS_Vtbl *)&vtblMSL))
return ResultFromScode(MAPI_E_INVALID_PARAMETER);

Validate_IMAPIContainer_OpenEntry(
pobj,
cbEntryID,
lpEntryID,
piid,
ulFlags,
lpulObjType,
lppUnk);
#endif

OBJ_EnterCriticalSection(pobj);

/* Get the real pims object in case pobj is a PIFLD or PMSL */
pims = pobj->pims;

peid = (PEID) lpEntryID;

/* If parameters are okay, see if this is an EntryID we understand. */

if (cbEntryID && FIsInvalidEID(cbEntryID, peid, pims))
{
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

if (cbEntryID == 0 || FIsRoot(peid))
fEIDIsRoot = TRUE;

/* determine if the caller wants modification access */
fModify = (ulFlags & (MAPI_MODIFY | MAPI_BEST_ACCESS)) != 0;
fBestAcc = (ulFlags & MAPI_BEST_ACCESS) != 0;

/* Fail if attempting to open an object for */
/* modification in a read-only store. */

if (fModify
&& !OBJ_TestFlag(pims, OBJF_MODIFY))
{
if (fBestAcc)
fModify = FALSE;
else
{
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}
}

/* Open the object */

if (fEIDIsRoot)
{
PEID peidRoot = NULL;
CHAR ch = '\0';
MAPIUID uid;

if (piid && !FQueryInterface(OT_FOLDER, piid))
{
hr = ResultFromScode(E_NOINTERFACE);
goto exit;
}

GetResourceUID(pims, &uid);

hr = HrConstructEID(&uid, &pims->lmr, (LPSTR) &ch, &peidRoot);
if (hr != hrSuccess)
goto exit;

hr = HrNewIFLD(peidRoot, pims, fModify, &pifld);

if (hr == hrSuccess)
hr = HrSetInternalProps(&pims->lmr, cpropIFLDInternal,
&(pifld->pval), &(pifld->cval), peidRoot, peidRoot, 0);

LMFree(&pims->lmr, peidRoot);

if (hr != hrSuccess)
goto exit;

*lppUnk = (LPUNKNOWN) pifld;
*lpulObjType = MAPI_FOLDER;
}
else
{
hr = HrGetParentEID(&pims->lmr, peid, &peidParent);
if (hr != hrSuccess)
goto exit;

if (FIsMessage(peid))
{
ULONG ulSeqNum;

if (piid && !FQueryInterface(OT_MESSAGE, piid))
{
hr = ResultFromScode(E_NOINTERFACE);
goto exit;
}

hr = HrGetSequenceNum(peid, &ulSeqNum);

hr = HrNewIMSG(peid, pims, FALSE, fModify, ulSeqNum, NULL, &pimsg);

if (GetScode(hr) == MAPI_E_SUBMITTED && fBestAcc && fModify)
{
fModify = FALSE;
hr = HrNewIMSG(peid, pims, FALSE, fModify, ulSeqNum, NULL, &pimsg);
}

if (hr != hrSuccess)
goto exit;

*lppUnk = (LPUNKNOWN) pimsg;
*lpulObjType = MAPI_MESSAGE;
}
else /* a folder */
{
if (!FIsFolder(peid))
{
TrapSz("Logic error");
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

if (piid && !FQueryInterface(OT_FOLDER, piid))
{
hr = ResultFromScode(E_NOINTERFACE);
goto exit;
}

hr = HrNewIFLD(peid, pims, fModify, &pifld);
if (hr != hrSuccess)
goto exit;

hr = HrSetInternalProps(&pims->lmr, cpropIFLDInternal,
&(pifld->pval), &(pifld->cval), peid, peidParent, 0);
if (hr != hrSuccess)
goto exit;

*lppUnk = (LPUNKNOWN) pifld;
*lpulObjType = MAPI_FOLDER;
}
}

exit:
AssertSz(hr == hrSuccess || HR_FAILED(hr), "No Warnings expected");

LMFree(&pims->lmr, peidParent);

if (hr != hrSuccess)
{
UlRelease(pimsg);
UlRelease(pifld);
}

OBJ_LeaveCriticalSection(pobj);

DebugTraceResult(IMS_OpenEntry, hr);
return HrCheckHr(hr, IMsgStore_OpenEntry);
}

/*
* IMS_SetReceiveFolder
*
* Purpose:
* Sets the receive folder for a particular message class. A
* message class is a string with "." delimiters, e.g
* "IPM.Note". This method also removes a setting if the
* EntryID parameter (see below) is NULL. Because there must
* be only one set of receive folder settings per store (NOT
* per logon), the settings are stored on disk in an OLE2
* docfile, and all access to them is done on disk, not in
* memory (see recfldr.c, recfldr.h for details).
*
* Arguments:
* pims Pointer to the object.
* szMessageClass String identifying a message class. If
* NULL, then the default receive folder
* is set.
* ulFlags Flags.
* cbEntryID Size of lpEntryID.
* lpEntryID Pointer to the identifier of a
* particular folder in the store. If
* this pointer is NULL, the receive
* folder setting is removed.
*
* Returns:
* HRESULT
*
* Side effects:
* IMS_SetReceiveFolder will not actually check to see if the
* EntryID it is given exists in the store at this time, so
* will go ahead and create an invalid receive folder setting
* for that particular message class.
*
* Errors:
* MAPI_E_INVALID_ENTRYID The store doesn't recognize this
* EntryID as belonging to it.
*/
STDMETHODIMP IMS_SetReceiveFolder(PIMS pims, LPTSTR szMessageClass,
ULONG ulFlags, ULONG cbEntryID, LPENTRYID lpEntryID)
{
SCODE sc;
HRESULT hr = hrSuccess;
TCHAR rgchDefMsgClass[] = TEXT("");
LPTSTR szNormalizedClass = NULL;
RFN rfn;
PEID peid;

/* Check input parameters. */

MS_ValidateParameters(
pims,
IMsgStore,
SetReceiveFolder,
(pims,
szMessageClass,
ulFlags,
cbEntryID,
lpEntryID));

#ifdef VALIDATE
if (ulFlags & MAPI_UNICODE)
return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
#endif

IMS_EnterCriticalSection(pims);

if (!OBJ_TestFlag(pims, OBJF_MODIFY))
{
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}

peid = (PEID) lpEntryID;

/* don't allow removal of the default settings */
if (peid == NULL &&
(szMessageClass == NULL || *szMessageClass == '\0'))
{
hr = ResultFromScode(MAPI_E_CALL_FAILED);
goto exit;
}

/* If parameters are okay, see if this is an EntryID we understand. */

if (cbEntryID && FIsInvalidEID(cbEntryID, peid, pims))
{
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

/* The RFS module always expects a valid string */
if (!szMessageClass)
szMessageClass = rgchDefMsgClass;

/* Make a copy of the szMessageClass that is normalized */

sc = LMAlloc(&pims->lmr, Cbtszsize(szMessageClass), &szNormalizedClass);
if (sc != S_OK)
{
hr = ResultFromScode(sc);
goto exit;
}
lstrcpy (szNormalizedClass, szMessageClass);
CharUpper (szNormalizedClass);

if (cbEntryID)
{
PRFN prfnExisting = NULL;

rfn.szClass = szNormalizedClass;
rfn.szName = peid->szPath;

hr = GetRFN(pims->prfs, szNormalizedClass, &prfnExisting);
if (hr != hrSuccess)
{
if (GetScode(hr) == MAPI_E_NOT_FOUND)
hr = hrSuccess;
else
goto exit;
}
else
{
/* Remove the existing receive folder setting, but only if it */
/* _exactly_ matches the one that we're adding. */
/* //$ Are message classes case-sensitive? */

if (!lstrcmp(szMessageClass, prfnExisting->szClass))
hr = DeleteRFN(pims->prfs, szNormalizedClass);

FreeRFN(prfnExisting);

if (hr != hrSuccess)
goto exit;
}

/* WARNING: If the addition fails, we can't easily */
/* revert to a previous version of the RFS settings. */

hr = AddRFN(pims->prfs, &rfn);
/* if ( hr ), fall through to exit */
}
else
{
hr = DeleteRFN(pims->prfs, szNormalizedClass);
/* if ( hr ), fall through to exit */
}

exit:
LMFree(&pims->lmr, szNormalizedClass);
IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_SetReceiveFolder, hr);
return HrCheckHr(hr, IMsgStore_SetReceiveFolder);
}

/*
* IMS_GetReceiveFolder
*
* Purpose:
* Obtains the receive folder setting for a particular message
* class and other information about the receive behavior of
* that message class. This function obtains the EntryID of
* the folder where messages of a specific class are placed.
* If szMessageClass does not explicitly set a receive
* folder, then the receive folder of the first superclass of
* szMessageClass which does explicitly set a receive folder
* is returned. Whichever message class that explicitly sets
* the receive folder is returned in pszExplicitClass. For
* example, if the receive folder of the message class
* "IPM.Note" has been set to the EntryID of the Inbox and an
* application calls GetReceiveFolder() on the message class
* "IPM.Note.Phone," the EntryID of the Inbox is returned as
* the lppEntryID, and "IPM.Note" is retuned in
* pszExplicitClass. The converse is not true: if the
* receive folder setting of "IPM.Note.Phone" is the Inbox and
* the client asks for the setting of "IPM.Note", NULL is
* returned.
*
* Arguments:
* pims Pointer to the object.
* szMessageClass Identifies the particular message
* class. If this pointer is NULL, then
* the default is returned.
* ulFlags Flags.
* lpcbEntryID Address of the location in which to
* return the size of the EntryID in
* *lppEntryID.
* lppEntryID Address of the location in which to
* return a pointer to an EntryID which is
* the identifier of the receive folder.
* pszExplicitClass Address of the location in which to
* return a pointer a buffer containing
* the message class that explicitly sets
* its receive folder to *lppEntryID. If
* NULL indicates that no class name
* should be returned. If it is exactly
* equal to szMessageClass, nothing is
* returned (*pszExplicitClass == NULL).
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* None.
*/
STDMETHODIMP IMS_GetReceiveFolder(PIMS pims, LPTSTR szMessageClass,
ULONG ulFlags, ULONG *lpcbEntryID, LPENTRYID *lppEntryID,
LPTSTR *pszExplicitClass)
{
HRESULT hr = hrSuccess;
ULONG cbeid = 0L;
PEID peid = NULL;
LPTSTR szExCls = NULL;
PRFN prfn = NULL;
TCHAR rgchDefMsgClass[] = TEXT("");
LPTSTR szNormalizedClass = NULL;
MAPIUID uid;
SCODE sc = S_OK;

/* Check input parameters. */

MS_ValidateParameters(
pims,
IMsgStore,
GetReceiveFolder,
(pims,
szMessageClass,
ulFlags,
lpcbEntryID,
lppEntryID,
pszExplicitClass));

#ifdef VALIDATE
if (ulFlags & MAPI_UNICODE)
return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
#endif

IMS_EnterCriticalSection(pims);

*lpcbEntryID = 0L;
*lppEntryID = NULL;

if (pszExplicitClass)
*pszExplicitClass = NULL;

/* The RFS module always expects a valid string */

if (!szMessageClass)
szMessageClass = rgchDefMsgClass;

/* Make a copy of the szMessageClass that is normalized */

sc = LMAlloc(&pims->lmr, Cbtszsize(szMessageClass), &szNormalizedClass);
if (sc != S_OK)
{
hr = ResultFromScode(sc);
goto exit;
}
lstrcpy (szNormalizedClass, szMessageClass);
CharUpper (szNormalizedClass);

hr = GetRFN(pims->prfs, szNormalizedClass, &prfn);
if (hr != hrSuccess)
goto exit;

/* Allocate and set return variables. */

GetResourceUID(pims, &uid);

hr = HrConstructEID(&uid, &pims->lmr, prfn->szName, &peid);
if (hr != hrSuccess)
goto exit;

cbeid = CbEID(peid);

if (pszExplicitClass)
{
sc = LMAlloc(&pims->lmr, Cbtszsize(prfn->szClass), &szExCls);
if (sc != S_OK)
{
hr = ResultFromScode(sc);
goto exit;
}

lstrcpy(szExCls, prfn->szClass);
*pszExplicitClass = szExCls;
}

*lpcbEntryID = cbeid;
*lppEntryID = (LPENTRYID) peid;

exit:
FreeRFN(prfn);
LMFree(&pims->lmr, szNormalizedClass);
if (hr != hrSuccess)
{
LMFree(&pims->lmr, peid);
LMFree(&pims->lmr, szExCls);
}

IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_GetReceiveFolder, hr);
return HrCheckHr(hr, IMsgStore_GetReceiveFolder);
}

STDMETHODIMP IMS_GetReceiveFolderTable(PIMS pims, ULONG ulFlags,
LPMAPITABLE *lppTable)
{
MS_ValidateParameters(
pims,
IMsgStore,
GetReceiveFolderTable,
(pims,
ulFlags,
lppTable));

#ifdef VALIDATE
if (ulFlags & MAPI_UNICODE)
return ResultFromScode(MAPI_E_BAD_CHARWIDTH);
#endif

DebugTraceSc(IMS_GetReceiveFolderTable, MAPI_E_NO_SUPPORT);
return ResultFromScode(MAPI_E_NO_SUPPORT);
}

/*
* IMS_StoreLogoff
*
* Purpose:
* Allows the orderly release of a store under client control.
* Use of this function to release a store allows some client
* control over what MAPI will do about transport activity.
* The client can either put itself in the loop by setting the
* appropriate flags, or it can allow MAPI to either abort the
* sending of mail or to complete it prior to invalidating the
* object. This behavior will only occur when the client is
* the only application which is using the message store, and
* will happen during release of the store object. If
* another client is still using the store, the store object
* will remember the flags and will issue the call during the
* final release.
*
* Arguments:
* pims Pointer to the object.
* pulFlags Flags. The following are defined as input:
* LOGOFF_NO_WAIT Don't wait for the transports.
* All outboutnd mail that is
* ready to be sent will be sent.
* Control is returned to the
* client immediately.
* LOGOFF_ORDERLY Don't wait for the transports.
* Any currently in-process
* message on the store is
* completed; no new ones are

*                                  started.  Control is returned 
* to the client immediately.
* LOGOFF_PURGE Same as LOGOFF_NO_WAIT but
* PurgeQueues() called for
* appropriate transports and
* client waits for completion.
* LOGOFF_ABORT Any transport activity on this
* store should be aborted.
* Control is returned to the
* client when abort completes.
* LOGOFF_QUIET If any transport activity is
* taking place, the logoff will
* not occur.
* The following are defined as output flags:
* LOGOFF_COMPLETE All resources
* associated with the
* store have been
* released and the object
* invalidated.
* LOGOFF_INBOUND A message is currently
* coming into the store
* from one or more
* transports.
* LOGOFF_OUTBOUND A message is currently
* being sent from the
* store by one or more
* transports.
* LOGOFF_OUTBOUND_QUEUE Messages are currently
* in the outbound queue
* for the store.
*
* Returns:
* HRESULT
*
* Side effects:
* See flag description for possible side effects.
*
* Errors:
* Various.
*/

STDMETHODIMP IMS_StoreLogoff(PIMS pims, ULONG * pulFlags)
{
HRESULT hr = 0;
LPMAPISUP psup = NULL;

MS_ValidateParameters(
pims,
IMsgStore,
StoreLogoff,
(pims,
pulFlags));

IMS_EnterCriticalSection(pims);

pims->ulFlagsSLT = *pulFlags;
*pulFlags = LOGOFF_COMPLETE;

IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_StoreLogoff, hr);
return HrCheckHr(hr, IMsgStore_StoreLogoff);
}

/*
* IMS_AbortSubmit
*
* Purpose:
* Removes the current message from the submission queue.
* Since this is not implemented in IMessage on IStorage, we
* must do it ourselves.
*
* Arguments:
* lpMS Pointer to the message store.
* cbEntryID the size of the entry ID
* lpEntryID the entry ID of the message to abort
* ulFlags Flags. Reserved for future use. Must be zero.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NOT_IN_QUEUE The message was never successfully
* submitted (and is thus not in the
* outgoing queue).
* MAPI_E_UNABLE_TO_ABORT The underlying messaging system no
* longer allows the submission to be
* cancelled.
*/
STDMETHODIMP IMS_AbortSubmit(PIMS pims, ULONG cbEntryID, LPENTRYID lpEntryID,
ULONG ulFlags)
{
HRESULT hr = hrSuccess;
PIMSG pimsg = NULL;
ULONG ulObjType;
BOOL fClearSpooler = FALSE;
ULONG ulSF;
PEID peid;

MS_ValidateParameters(
pims,
IMsgStore,
AbortSubmit,
(pims,
cbEntryID,
lpEntryID,
ulFlags));

IMS_EnterCriticalSection(pims);

peid = (PEID) lpEntryID;

/* Note that we don't allow a NULL entryid here, because the root */
/* folder is not a valid input to AbortSubmit. */

if (FIsInvalidEID(cbEntryID, peid, pims)
&& !FIsMessage(peid))
{
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

/* unlock the message */
/* make this store look like the spooler so we can open it if locked */

if (!OBJ_TestFlag(pims, MSF_SPOOLER))
{
OBJ_SetFlag(pims, MSF_SPOOLER);
fClearSpooler = TRUE;
}

hr = pims->lpVtbl->OpenEntry(pims, cbEntryID, lpEntryID,
NULL, MAPI_MODIFY, &ulObjType, (LPUNKNOWN *) &pimsg);
if (hr != hrSuccess)
goto exit;

/* If we can't get PR_SUBMIT_FLAGS from the message, then either */
/* the message hasn't been submitted, or something else is broken. */
/* In any case, we can't abort the submit. */
/* If the message is locked already by the spooler, then we also */
/* can't abort the submit. */

hr = HrGetSingleProp((LPMAPIPROP) pimsg, &pims->lmr, PR_SUBMIT_FLAGS, &ulSF);
if ((hr != hrSuccess) || (ulSF & SUBMITFLAG_LOCKED))
{
hr = ResultFromScode(MAPI_E_UNABLE_TO_ABORT);
goto exit;
}

hr = HrSetFlags(pimsg, UNSET, PR_MESSAGE_FLAGS, MSGFLAG_SUBMIT);
if (hr != hrSuccess)
goto exit;

hr = HrSetFlags(pimsg, UNSET, PR_SUBMIT_FLAGS, SUBMITFLAG_LOCKED);
if (hr != hrSuccess)
goto exit;

hr = pimsg->lpVtbl->SaveChanges(pimsg, KEEP_OPEN_READWRITE);
if (hr != hrSuccess)
goto exit;

hr = HrUpdateOutgoingQueue(pims, NULL, (PEID) lpEntryID,
TABLE_ROW_DELETED);

exit:
UlRelease(pimsg);

if (fClearSpooler)
OBJ_ClearFlag(pims, MSF_SPOOLER);

IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_AbortSubmit, hr);
return HrCheckHr(hr, IMsgStore_AbortSubmit);
}

/*
* IMS_GetOutgoingQueue
*
* Purpose:
* Returns a MAPI Table Object of the queue of messages
* waiting to be sent.
*
* Arguments:
* pims Pointer to a Spooler Message Store Object.
* ulFlags Reserved for future use. Must be zero.
* lppTable Location to return the new table object.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* None.
*/
STDMETHODIMP IMS_GetOutgoingQueue(PIMS pims, ULONG ulFlags,
LPMAPITABLE *lppTable)
{
SCODE sc = S_OK;
HRESULT hr = hrSuccess;
LPMAPITABLE ptbl = NULL;
BOOL fInMutex = FALSE;

MS_ValidateParameters(
pims,
IMsgStore,
GetOutgoingQueue,
(pims,
ulFlags,
lppTable));

IMS_EnterCriticalSection(pims);

if (!OBJ_TestFlag(pims, MSF_SPOOLER))
{
hr = ResultFromScode(MAPI_E_NO_SUPPORT);
goto exit;
}

/* If the file mutex doesn't yet exist on this process, create it. */

if (pims->hOGQueueMutex == NULL)
{
hr = HrCreateOGQueueMutex(&pims->hOGQueueMutex);
if (hr != hrSuccess)
goto exit;
}

/* Get the file mutex so that we can use the file (and change it) */
/* without crossing paths with another process. */

WaitForSingleObject(pims->hOGQueueMutex, INFINITE);
fInMutex = TRUE;

/* Create a new table if there currently is none */
if (pims->lptblOutgoing == NULL)
{
hr = HrNewOutgoingTableData(pims);
if (hr != hrSuccess)
goto exit;
}

/* open a view on the table */
hr = pims->lptblOutgoing->lpVtbl->HrGetView(pims->lptblOutgoing,
NULL, OutgoingViewRelease, (ULONG) pims, &ptbl);
if (hr != hrSuccess)
goto exit;

pims->cOutgoingViews++;

*lppTable = ptbl;

Assert(hrSuccess == hr);

exit:
AssertSz(hr == hrSuccess || HR_FAILED(hr),
"Unexpected warning return");

if (fInMutex)
ReleaseMutex(pims->hOGQueueMutex);

IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_GetOutgoingQueue, hr);
return HrCheckHr(hr, IMsgStore_GetOutgoingQueue);
}

/*
* IMS_SetLockState
*
* Purpose:
* Allows the spooler to lock a message so that no one else
* can modify it while the spooler processes it.
*
* Arguments:
* pims Pointer to the Message Store Object.
* lpMessage The message object to be locked
* ulFlags control flags
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* None.
*/
STDMETHODIMP IMS_SetLockState(PIMS pims, LPMESSAGE lpMessage, ULONG ulFlags)
{
SCODE sc = S_OK;
HRESULT hr = hrSuccess;
PIMSG pimsg = (PIMSG) lpMessage;
ULONG ulSF;
LPSPropValue pval = NULL;

MS_ValidateParameters(
pims,
IMsgStore,
SetLockState,
(pims,
lpMessage,
ulFlags));

IMS_EnterCriticalSection(pims);

if (!OBJ_TestFlag(pims, MSF_SPOOLER))
{
hr = ResultFromScode(MAPI_E_NO_SUPPORT);
goto exit;
}

hr = HrGetSingleProp((LPMAPIPROP) pimsg, &pims->lmr, PR_SUBMIT_FLAGS, &ulSF);
if (hr != hrSuccess)
{
if (GetScode(hr) == MAPI_E_NOT_FOUND)
{
ULONG ulMF;

hr = HrGetSingleProp((LPMAPIPROP) pimsg, &pims->lmr, PR_MESSAGE_FLAGS, &ulMF);
if (hr != hrSuccess)
{
if (GetScode(hr) == MAPI_E_NOT_FOUND)
hr = ResultFromScode(MAPI_E_CORRUPT_STORE);

goto exit;
}

if (ulMF & MSGFLAG_SUBMIT)
hr = ResultFromScode(MAPI_E_CORRUPT_STORE);
else
hr = ResultFromScode(MAPI_E_NOT_IN_QUEUE);
}
goto exit;
}

/* set the lock state, if the message is already in the correct state */
/* just get outta here */

if (ulFlags & MSG_LOCKED)
{
if (!(ulSF & SUBMITFLAG_LOCKED))
ulSF |= SUBMITFLAG_LOCKED;
else
goto exit;
}
else
/* unlock */
{
if (ulSF & SUBMITFLAG_LOCKED)
ulSF &= ~SUBMITFLAG_LOCKED;
else
goto exit;
}

hr = HrSetSingleProp((LPMAPIPROP) pimsg, &pims->lmr, PR_SUBMIT_FLAGS, &ulSF);
if (hr != hrSuccess)
goto exit;

hr = pimsg->lpVtbl->SaveChanges(pimsg, KEEP_OPEN_READWRITE);
if (hr != hrSuccess)
goto exit;

/* No need to call ChangeTable to update tables because the SaveChanges
* call above just did that.
*/

exit:
IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_SetLockState, hr);
return HrCheckHr(hr, IMsgStore_SetLockState);
}

/*
* IMS_FinishedMsg
*
* Purpose:
* Allows the Spooler to inform the message store that it has
* finished processing a message (cancels a previous
* MDBLockMsg).
*
* Arguments:
* pims Pointer to a Message Store Object.
* ulFlags Reserved for future use. Ignored.
* lpEntryID EntryID of message that was locked.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* None.
*/
STDMETHODIMP IMS_FinishedMsg(PIMS pims, ULONG ulFlags, ULONG cbEntryID,
LPENTRYID lpEntryID)
{
PIMSG pimsg = NULL; /* opened message */
PIFLD pifldParent = NULL; /* parent folder of this message */
ULONG ulObjectType;
HRESULT hr = hrSuccess;
SCODE sc = S_OK;
PEID peid = (PEID) lpEntryID;


MS_ValidateParameters(
pims,
IMsgStore,
FinishedMsg,
(pims,
ulFlags,
cbEntryID,
lpEntryID));

IMS_EnterCriticalSection(pims);

if (!OBJ_TestFlag(pims, MSF_SPOOLER))
{
hr = ResultFromScode(MAPI_E_NO_SUPPORT);
goto exit;
}

if (FIsInvalidEID(cbEntryID, peid, pims))
{
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

hr = pims->lpVtbl->OpenEntry(pims, cbEntryID, lpEntryID,
NULL, MAPI_MODIFY, &ulObjectType, (LPUNKNOWN *) &pimsg);
if (hr != hrSuccess)
goto exit;

Assert(ulObjectType == MAPI_MESSAGE);

/* update the parent folder */
hr = HrOpenParent(pims, peid, MAPI_MODIFY, &pifldParent);
if (hr != hrSuccess)
goto exit;

/* unlock the message */
hr = HrSetFlags(pimsg, UNSET, PR_MESSAGE_FLAGS,
MSGFLAG_UNSENT | MSGFLAG_SUBMIT);
if (hr != hrSuccess)
goto exit;

/* Mark the message read */
hr = HrSetFlags(pimsg, SET, PR_MESSAGE_FLAGS, MSGFLAG_READ);
if (hr != hrSuccess)
goto exit;

/* Clear submitflag_locked and save the message */
hr = HrSetFlags(pimsg, UNSET, PR_SUBMIT_FLAGS, SUBMITFLAG_LOCKED);
if (hr != hrSuccess)
goto exit;

hr = pimsg->lpVtbl->SaveChanges(pimsg, KEEP_OPEN_READWRITE);
if (hr != hrSuccess)
goto exit;

hr = pims->psup->lpVtbl->DoSentMail(pims->psup, 0L, (LPMESSAGE) pimsg);
if (hr != hrSuccess)
goto exit;
pimsg = NULL;

Assert(pims->lptblOutgoing);

hr = HrUpdateOutgoingQueue(pims, NULL, (PEID) lpEntryID,
TABLE_ROW_DELETED);

exit:
UlRelease(pifldParent);
UlRelease(pimsg);

IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_FinishedMsg, hr);
return HrCheckHr(hr, IMsgStore_FinishedMsg);
}

/*
* IMS_NotifyNewMail
*
* Purpose:
* Spooler tells us when to tell clients about a new message.
* Passed in on the call is the exact notification structure
* we need to give the client.
*
* Stubbed for now.
*
* Arguments:
* pims Pointer to the object.
* pntf Pointer to the newmail notification structure.
* read/write.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* None.
*/
STDMETHODIMP IMS_NotifyNewMail(PIMS pims, LPNOTIFICATION pntf)
{
HRESULT hr = hrSuccess;
LPNOTIFKEY lpKey = NULL;
ULONG ulFlags = 0;
PEID peidMsg;
PEID peidFld;
ULONG cbEIDMsg;
ULONG cbEIDFld;

MS_ValidateParameters(
pims,
IMsgStore,
NotifyNewMail,
(pims,
pntf));

IMS_EnterCriticalSection(pims);

if (!OBJ_TestFlag(pims, MSF_SPOOLER))
{
hr = ResultFromScode(MAPI_E_NO_SUPPORT);
goto exit;
}

if (!pims->psup)
{
hr = ResultFromScode(MAPI_E_CALL_FAILED);
goto exit;
}

cbEIDMsg = pntf->info.newmail.cbEntryID;
peidMsg = (PEID) pntf->info.newmail.lpEntryID;

cbEIDFld = pntf->info.newmail.cbParentID;
peidFld = (PEID) pntf->info.newmail.lpParentID;

if ( cbEIDMsg == 0
|| FIsInvalidEID(cbEIDMsg, peidMsg, pims)
|| !FIsMessage(peidMsg)
|| cbEIDFld == 0
|| FIsInvalidEID(cbEIDFld, peidFld, pims)
|| !FIsFolder(peidFld))
{
TraceSz("SMS: Bad entryid given to NotifyNewMail");
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

/* Generate the key we use for notifications. First, get the key */
/* for the folder that the message arrived into. */

hr = HrGetSMSStandardNotifKey(pims, peidFld, &lpKey);
if (hr != hrSuccess)
goto exit;

hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, pntf, &ulFlags);
if (hr != hrSuccess)
goto exit;

FreeNull(lpKey);
lpKey = NULL;
ulFlags = 0;

/* Now, get the key for the entire store, and send to that key also. */

hr = HrGetSMSStandardNotifKey(pims, NULL, &lpKey);
if (hr != hrSuccess)
goto exit;

hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, pntf, &ulFlags);
if (hr != hrSuccess)
goto exit;

exit:
FreeNull(lpKey);

IMS_LeaveCriticalSection(pims);

DebugTraceResult(IMS_NotifyNewMail, hr);
return HrCheckHr(hr, IMsgStore_NotifyNewMail);
}

/* Internal Functions */

BOOL IMS_IsInvalid(PIMS pims)
{
return (IsBadWritePtr(pims, sizeof(IMS)) || pims->lpVtbl != &vtblIMS);
}

/***************************************************************************
- OutgoingViewRelease
-
* Purpose:
* Call back function from itable on release of a view
* removes the view from the list of open views
* releases the table if there are no more open views on it
*
* Arguments:
* ulCallerData pointer to folder object
* lptbl pointer to the table on which this is a view
* lpvtView pointer to the view that was released
*
*
*/
STDAPI_(void) OutgoingViewRelease(ULONG ulCallerData, LPTABLEDATA lptbl,
LPMAPITABLE lpvtView)
{
PIMS pims; /* store who owns this view */
ULONG ulViewsLeft; /* number of open views left */

pims = (PIMS) ulCallerData;

/* do nothing if the message store is gone */
if (IMS_IsInvalid(pims))
return;

IMS_EnterCriticalSection(pims);

AssertSz(pims->lptblOutgoing == lptbl,
"Different table data given to OutgoingViewRelease");

ulViewsLeft = --(pims->cOutgoingViews);

/* release the table data if the viewlist is empty */
if (ulViewsLeft == 0)
{
UlRelease(lptbl);
pims->lptblOutgoing = NULL;
}

IMS_LeaveCriticalSection(pims);

return;
}

/*
* HrNewIMS
*
* Purpose:
* Allocates and initializes an IMS object (internal
* implementation of IMsgStore).
*
* Arguments:
* szStorePath Path name of directory which is the
* "root" of this message store.
* szStoreProps Filename of IMsgStore properties
* docfile in the store root.
* pmsp pointer to the MS provider object.
* pmsl Pointer to the MS logon object given to
* MAPI when this store is created.
* prfs Pointer to a context for accessing
* receive folder settings.
* pps pointer to our profile section.
* psup Pointer to MAPI Support Object.
* fCreate TRUE if the function should create the docfile
* containing IMS properties (FALSE opens existing).
* ppims Location to return the address of the
* newly created IMS object.
*
* Returns:
* HRESULT
*
* Side effects:
* Does AddRef() of support object.
*
* Errors:
* MAPI_E_NOT_ENOUGH_MEMORY Could not allocate memory for
* the object.
*/
HRESULT HrNewIMS(LPTSTR szStorePath, LPTSTR szStoreProps, PMSP pmsp, PMSL pmsl,
PRFS prfs, LPPROFSECT pps, LPMAPISUP psup, BOOL fCreate, PIMS *ppims)
{
SCODE sc = S_OK;
HRESULT hr;
HRESULT hrStg = hrSuccess;
LPTSTR szPropFull = NULL;
PIMS pimsNew = NULL;
PEID peid = NULL;
LPMESSAGE lpmsg = NULL;
BOOL fDoneCreate = FALSE;
LPMSGSESS pmsgsess = NULL;
LPSPropValue pvalDLLName = NULL;

AssertSz(szStorePath, "Bad szStorePath");
AssertSz(szStoreProps, "Bad szStoreProps");
AssertSz(pmsl, "Bad pmsl");
AssertSz(prfs, "Bad prfs");
AssertSz(ppims, "Bad ppims");

*ppims = NULL;

/* Begin by creating or opening the message store's property file. */
/* This implementation of the sample message store puts its message */
/* store properties in a file called MSGSTORE.PRP in the root folder */
/* of the store. If we're opening an existing store, then we'll read */
/* PR_RECORD_KEY from this file below. If we're creating the file, we */
/* don't actually use it in this function; however, we still need to */
/* create the file. */

hr = HrOpenIMsgSession(&pmsgsess);
if (hr != hrSuccess)
goto hr_err;

hr = HrAppendPath(szStorePath, szStoreProps, &szPropFull);
if (hr != hrSuccess)
goto hr_err;

hr = HrOpenIMsg(pmsgsess, szPropFull, &pmsp->lmr, psup, fCreate, FALSE,
TRUE, &lpmsg);
if (hr != hrSuccess)
goto hr_err;

if (fCreate)
fDoneCreate = TRUE;

/* Allocate and fill in the new object. */

sc = LMAllocZ(&pmsp->lmr, sizeof(IMS), &pimsNew);
if (sc != S_OK)
goto sc_err;

OBJ_Initialize(pimsNew, &vtblIMS, OT_MSGSTORE, pimsNew, &pmsl->cs);

pimsNew->pmsl = pmsl;
pimsNew->pmsp = pmsp;
pimsNew->prfs = prfs;
pimsNew->psup = psup;
pimsNew->lmr = pmsl->lmr;
pimsNew->eidStore.cb = 0L;
pimsNew->eidStore.lpb = NULL;
pimsNew->lptblOutgoing = NULL;
pimsNew->cOutgoingViews = 0L;
pimsNew->ulOQConn = 0L;
pimsNew->ulTblConn = 0L;
pimsNew->pmsgsess = pmsgsess;
pimsNew->ulFlagsSLT = LOGOFF_ABORT;

sc = ScAlloc(Cbtszsize(szStorePath), (PPV) &pimsNew->szStorePath);
if (sc != S_OK)
goto sc_err;

lstrcpy(pimsNew->szStorePath, szStorePath);

pimsNew->szProps = szPropFull;

/* Fill in the uidResource.
* This is used as the PR_STORE_RECORD_KEY and
* as the UID contained in EntryIDs.
*/
if (fCreate)
{
hr = psup->lpVtbl->NewUID(psup, &pimsNew->uidResource);
if (hr != hrSuccess)
goto hr_err;
}
else
{
LPSPropValue pval;

/* Read out the PR_RECORD_KEY from the store. */
/* DO NOT READ THE PR_STORE_RECORD_KEY as that */
/* gets wrapped back to pimsNew->uidResource and */
/* HAS NOT BEEN FILLED IN YET! */

hr = HrGetOneProp((LPMAPIPROP) lpmsg, PR_RECORD_KEY, &pval);
if (hr != hrSuccess)
goto hr_err;

AssertSz(pval[0].Value.bin.cb == sizeof(MAPIUID),
"Corrupted data returned from GetProps");

pimsNew->uidResource = *((LPMAPIUID) pval[0].Value.bin.lpb);

LMFree(&pmsp->lmr, pval);
}

/* Generate the PR_STORE_ENTRYID property in memory. */

hr = HrConstructEID(&pimsNew->uidResource, &pmsp->lmr,
pimsNew->szStorePath, &peid);
if (hr != hrSuccess)
goto hr_err;

hr = HrGetOneProp((LPMAPIPROP)pps, PR_PROVIDER_DLL_NAME, &pvalDLLName);
if (hr != hrSuccess)
goto hr_err;

hr = WrapStoreEntryID(0, pvalDLLName->Value.lpszA, CbEID(peid),
(LPENTRYID)peid, &pimsNew->eidStore.cb,
(LPENTRYID *) &pimsNew->eidStore.lpb);
if (hr != hrSuccess)
goto hr_err;

sc_err:
if (sc != S_OK)
hr = ResultFromScode(sc);

hr_err:
LMFree(&pmsp->lmr, pvalDLLName);
UlRelease(lpmsg);
LMFree(&pmsp->lmr, peid);

if (hr != hrSuccess)
{
if (fDoneCreate)
DeleteFile(szPropFull);

FreeNull(szPropFull);
if (pimsNew)
{
FreeNull(pimsNew->szStorePath);
LMFree(&pmsp->lmr, pimsNew->eidStore.lpb);
LMFree(&pmsp->lmr, pimsNew);
}

if (pmsgsess)
CloseIMsgSession(pmsgsess);
}
else
{
/* SUCCESS! */
*ppims = pimsNew;
}

DebugTraceResult(HrNewIMS, hr);
return hr;
}

/*
* HrInitIMSProps
*
* Purpose:
* Sets the initial (and for read-only properties, the only)
* values for the base properties of the Message Store Object:
* takes as input parameters the values of those properties
* that are specific to this store and calculates (hard-coded)
* the values of those properties that are the same for all
* stores created by the Microsoft Sample Store Provider.
* Also sets attributes.
*
* Arguments:
* pims Internal IMsgStore object instance.
* szPswd Logon Account Password.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* MAPI_E_NOT_ENOUGH_MEMORY Could not allocate space for
* the property arrays.
*/
HRESULT HrInitIMSProps(PIMS pims, LPTSTR szPswd)
{
HRESULT hr;
LPMESSAGE lpmsg = NULL;
LPSPropValue pval = NULL;
LPSPropProblemArray pprba = NULL;
LPSPropTagArray ptaga = NULL;
LPSPropAttrArray patra = NULL;
SCODE sc = S_OK;
LPSPropProblem pProbl = NULL;

#define cInitIMSProps 10
#define grfInitIMSProps ( PROPATTR_MANDATORY | PROPATTR_READABLE )

AssertSz(pims, "Bad pims");
NFAssertSz(pims->psup, "Bad support object");
AssertSz(szPswd, "Bad szPswd");

hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
pims->psup, TRUE, &lpmsg);
if (hr != hrSuccess)
goto exit;

/* Allocate the property arrays. */
hr = HrAllocPropArrays(cInitIMSProps, &pval, &ptaga, &patra);
if (hr != hrSuccess)
goto exit;

/* Initialize property value array and all property tags. */

ptaga->cValues = patra->cValues = cInitIMSProps;

pval[0].ulPropTag = ptaga->aulPropTag[0] = PR_STORE_ENTRYID;
pval[0].Value.bin.cb = pims->eidStore.cb;
pval[0].Value.bin.lpb = pims->eidStore.lpb;
pval[1].ulPropTag = ptaga->aulPropTag[1] = PR_STORE_RECORD_KEY;
pval[1].Value.bin.cb = sizeof(pims->uidResource);
pval[1].Value.bin.lpb = (LPBYTE) &pims->uidResource;
pval[2].ulPropTag = ptaga->aulPropTag[2] = PR_ENTRYID;
pval[2].Value.bin.cb = pims->eidStore.cb;
pval[2].Value.bin.lpb = pims->eidStore.lpb;
pval[3].ulPropTag = ptaga->aulPropTag[3] = PR_RECORD_KEY;
pval[3].Value.bin.cb = sizeof(pims->uidResource);
pval[3].Value.bin.lpb = (LPBYTE) &pims->uidResource;
pval[4].ulPropTag = ptaga->aulPropTag[4] = PR_DISPLAY_NAME;
pval[4].Value.LPSZ = pims->szStorePath;
pval[5].ulPropTag = ptaga->aulPropTag[5] = PR_SMS_PASSWORD;
pval[5].Value.LPSZ = szPswd;

/* We don't set support properties that are changed by READONLY */
/* when the store is open. Those are or'ed in during GetProps. */
/* See HrWrap_GetProps for details. */

pval[6].ulPropTag = ptaga->aulPropTag[6] = PR_STORE_SUPPORT_MASK;
pval[6].Value.ul = SMS_SUPPORTMASK;

pval[7].ulPropTag = ptaga->aulPropTag[7] = PR_OBJECT_TYPE;
pval[7].Value.l = MAPI_STORE;
pval[8].ulPropTag = ptaga->aulPropTag[8] = PR_FILENAME_SEQUENCE_NUMBER;
pval[8].Value.ul = 0x10000000;
pval[9].ulPropTag = ptaga->aulPropTag[9] = PR_MDB_PROVIDER;
pval[9].Value.bin.cb = sizeof(MAPIUID);
pval[9].Value.bin.lpb = (LPBYTE) &uidProvider;

/* Initialize the property attribute array. */

patra->aPropAttr[0] = grfInitIMSProps;
patra->aPropAttr[1] = grfInitIMSProps;
patra->aPropAttr[2] = grfInitIMSProps;
patra->aPropAttr[3] = grfInitIMSProps;
patra->aPropAttr[4] = grfInitIMSProps | PROPATTR_WRITEABLE;
patra->aPropAttr[5] = grfInitIMSProps;
patra->aPropAttr[6] = grfInitIMSProps;
patra->aPropAttr[7] = grfInitIMSProps;
patra->aPropAttr[8] = grfInitIMSProps;
patra->aPropAttr[9] = grfInitIMSProps;

/* Set the property values. */

hr = lpmsg->lpVtbl->SetProps(lpmsg, cInitIMSProps, pval, &pprba);
if (hr != hrSuccess) /* || pprba)*/
goto exit;
if(pprba)
{
for(pProbl = pprba->aProblem; pProbl < pprba->aProblem + pprba->cProblem; ++pProbl)
{
if(pProbl->ulPropTag != PR_STORE_SUPPORT_MASK)
goto exit;
}
LMFree(&pims->lmr, pprba);
pprba = NULL;
}


/* Set the property attributes. */

hr = SetAttribIMsgOnIStg(lpmsg, ptaga, patra, &pprba);
if (hr != hrSuccess) /* || pprba)*/
goto exit;
if(pprba)
{
for(pProbl = pprba->aProblem; pProbl < pprba->aProblem + pprba->cProblem; ++pProbl)
{
if(pProbl->ulPropTag != PR_STORE_SUPPORT_MASK)
goto exit;
}
LMFree(&pims->lmr, pprba);
pprba = NULL;
}



/* If we succeeded up to this point, commit the properties. */

hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);
if (hr != hrSuccess)
goto exit;

exit:
AssertSz(hr == hrSuccess || HR_FAILED(hr), "No Warnings expected");

if (pprba)
{
LMFree(&pims->lmr, pprba);
hr = ResultFromScode(MAPI_E_CALL_FAILED);
}

FreePropArrays(&pval, &ptaga, &patra);


UlRelease(lpmsg);

DebugTraceResult(InitIMSProps, hr);
return hr;
}

/*
* GetResourceUID
*
* Purpose:
* Returns the one UID that identifies this MAPI resource
* (message store).
*
* Arguments:
* pims Internal IMsgStore object instance.
* lpuid Location in which to return the value of the
* Resource UID.
*
* Returns:
* VOID
*
* Side effects:
* None.
*
* Errors:
* None.
*/
VOID GetResourceUID(PIMS pims, LPMAPIUID lpuid)
{
AssertSz(pims, "Bad pims");
AssertSz(lpuid, "Bad lpuid");

*lpuid = pims->uidResource;

return;
}

/*********************************************************
* FIsInvalidEID
*
* Purpose check if the peid points to an invalid EID
* Returns TRUE if it is invalid. This routine
* considers EIDs of length 0 invalid.
*
* Parameter
* cb number of bytes believed to be in peid
* peid pointer to the entryid
* pims store in which the object should be. May be NULL,
* in which case, no check of the uid is performed.
*/
BOOL FIsInvalidEID(ULONG cb, PEID peid, PIMS pims)
{
BOOL fInvalid;

fInvalid = (cb < CbNewEID(0)
|| cb > CbNewEID(MAX_PATH)
|| IsBadReadPtr(peid, (UINT) cb)
|| IsBadStringPtr(peid->szPath, (UINT) -1)
|| cb != CbEID(peid)
|| peid->bVersion != SMPMS_VERSION);

/* If the eid still looks good, and we were given the message store */
/* object, then do one final check of the uid in the eid versus the */
/* uid of the store. */

if (!fInvalid && pims)
{
MAPIUID uid;

GetResourceUID(pims, &uid);
fInvalid = !IsEqualMAPIUID(&uid, &peid->uidResource);
}

return fInvalid;
}

/*
* HrUniqueFileName
*
* Purpose:
* Returns a unique file name base that can be used by other
* parts of the store when a file needs to be created. Gets
* the PR_FILENAME_SEQUENCE_NUMBER property out of the message
* store object, uses its textized form as the unique name,
* increments it, and stores it back in the object. Sequence
* numbers begin at 0x10000000 and increment so that every
* file name returned is the same 8-character length. (See
* HrInitIMSProps.)
*
* Arguments:
* pims Message Store Object.
* lpulSeqNumber pointer to sequence number of this file name
* lppstrNewName Location in which to return a pointer to a
* buffer containing the unique file name.
*
* Returns:
* HRESULT
*
* Side effects:
* Increments the PR_FILENAME_SEQUENCE_NUMBER property of the
* message store object.
*
* Errors:
* All SetProps and SaveChanges errors. Also:
*
* MAPI_E_NOT_ENOUGH_MEMORY Could not allocate space for
* the return parameter.
*/
HRESULT HrUniqueFileName(PIMS pims, ULONG *lpulSeqNumber,
LPTSTR *pszNewName)
{
HRESULT hr = hrSuccess;
LPTSTR szFileName = NULL;
ULONG ulSeq = 0L;
LPMESSAGE lpmsg = NULL;

AssertSz(pims, "Bad pims");
AssertSz(pszNewName, "Bad pszNewName");

/* Allocate space for the return string */

hr = HrAlloc((CCH_NAME - CCH_EXT) * sizeof(TCHAR), (PPV) &szFileName);
if (hr != hrSuccess)
goto exit;

/* Get sequence number out of object, increment */
/* sequence number, and turn it into a string. */

hr = HrGetSingleProp((LPMAPIPROP) pims, &pims->lmr,
PR_FILENAME_SEQUENCE_NUMBER, &ulSeq);
if (hr != hrSuccess)
goto exit;

ulSeq++;

hr = HrOpenIMSPropsFileRetry(pims->pmsgsess, pims->szProps, &pims->lmr,
pims->psup, TRUE, &lpmsg);
if (hr != hrSuccess)
goto exit;

hr = HrSetOneROProp(lpmsg, &pims->lmr, PR_FILENAME_SEQUENCE_NUMBER, &ulSeq);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);
if (hr != hrSuccess)
goto exit;

/* If this wsprintf statement changes, fix HrConvertSzToHex below. */

wsprintf(szFileName, TEXT("%08lx"), ulSeq);

*pszNewName = szFileName;
*lpulSeqNumber = ulSeq;

exit:
UlRelease(lpmsg);

if (hr != hrSuccess)
FreeNull(szFileName);

DebugTraceResult(HrUniqueFileName, hr);
return hr;
}

/*
* HrConvertSzToHex
*
* Converts the string given into an equivalent number. The string must
* contain characters in the range 0-9, A-F, or a-f. If the routine finds
* characters outside these ranges in the string, it will terminate with
* the error MAPI_E_CALL_FAILED. The string must be at least 8 characters
* long (a 32-bit number is fully-specified by a 8 hex characters). The
* routine will use only the first 8 characters in the string, even if the
* string is longer than 8 characters. Note that only the first 8 characters
* must be within the proper range. Characters after the first 8 will be
* ignored.
*
* Parameters:
* szName: The string to convert.
* pulAnswer: A pointer to the location to return the converted answer.
*
* Errors:
* MAPI_E_CALL_FAILED -- when a character is out of range.
*/
static HRESULT HrConvertSzToHex(LPSTR szName, ULONG *pulAnswer)
{
HRESULT hr = hrSuccess;
CHAR *pch;
CHAR *pchMax;
ULONG ulAns = 0;
ULONG ichConv = 0;

/* String must be at least 8 hex chars long. It should have come from */
/* the HrUniqueFileName function above, which uses wsprintf to generate */
/* the string. */

if ( IsBadStringPtr(szName, (UINT) -1)
|| lstrlen(szName) < 8)
{
TrapSz("Bad input to function");
hr = ResultFromScode(MAPI_E_CALL_FAILED);
goto exit;
}

pch = szName;
pchMax = szName + lstrlen(szName);

/* Only convert the first 8 characters, no matter how long the string is. */
while(pch < pchMax && ichConv < 8)
{
ulAns <<= 4;

if (*pch >= '0' && *pch <= '9')
ulAns += (*pch - '0');
else if (*pch >= 'a' && *pch <= 'f')
ulAns += (*pch - 'a' + 0xA);
else if (*pch >= 'A' && *pch <= 'F')
ulAns += (*pch - 'A'+ 0xA);
else
{
TraceSz2("SampleMS: HrConvertSztoHex: char %c(%#x) out of range.\n",
*pch, *pch);
hr = ResultFromScode(MAPI_E_CALL_FAILED);
goto exit;
}

++pch;
++ichConv;
}

*pulAnswer = ulAns;

exit:
DebugTraceResult(HrConvertSzToHex, hr);
return hr;
}

/*
* HrGetSequenceNum
*
* This routine takes a message eid and gets the
* numerical equivalent of the base file name in the entryid. So, if the
* base name of the message was "00000005.msg", the function would return
* 0x00000005.
*
* Parameters:
* pimsg: the message object to update.
* pulSequenceNum: a pointer to the location to place the generated number.
*
*/
static HRESULT HrGetSequenceNum(PEID peid, ULONG *pulSequenceNum)
{
HRESULT hr;
LPSTR szBaseName;
ULONG ulSeqNum;

szBaseName = SzBaseName(peid);

hr = HrConvertSzToHex(szBaseName, &ulSeqNum);
if (hr != hrSuccess)
goto exit;

*pulSequenceNum = ulSeqNum;

exit:
DebugTraceResult(HrGetSequenceNum, hr);
return hr;
}

/*
* IMS_NeuterChildren, IMS_Neuter
*
* Purpose:
* Free all memory and resources associated with a logon. We
* cannot, however, remove the critical section: that must be
* done by the caller of this routine.
*
* Arguments:
* pims Pointer to the MS object.
*
* Returns:
* None.
*
* Side effects:
* None.
*
* Errors:
* None.
*/
void IMS_NeuterChildren(POBJ pobj, int iLevel)
{
#ifdef DEBUG
int i;

DebugTrace("SMS: ");
for (i = 0; i < iLevel; ++i)
DebugTrace(" ");

switch (pobj->wType)
{
case OT_MSGSTORE:
DebugTrace("IMsgStore (%s)", ((PIMS) pobj)->szStorePath);
break;
case OT_FOLDER:
DebugTrace("IMAPIFolder");
break;
case OT_MESSAGE:
DebugTrace("IMessage");
break;
case OT_ATTACH:
DebugTrace("IAttach");
break;
case OT_STREAM:
DebugTrace("IStream");
break;
}
DebugTrace(" (@%08lX,cRef=%ld)\n", pobj, pobj->cRef);
#endif

while (TRUE)
{
POBJ pobjChild;

pobjChild = pobj->pobjHead;

if (pobjChild == NULL)
break;

pobj->pobjHead = pobjChild->pobjNext;
IMS_NeuterChildren(pobjChild, iLevel + 1);
}

if (pobj->wType != OT_MSGSTORE)
{
LPFNNEUTER lpfnNeuter;

lpfnNeuter = rgfnNeuter[pobj->wType];

if (lpfnNeuter != 0)
lpfnNeuter(pobj);

pobj->lpVtbl = 0;
LMFree(&pobj->pims->lmr, pobj);
}
}

void IMS_Neuter(PIMS pims)
{
HRESULT hr;
ULONG ulFlags;


if (pims->ulOQConn)
{
pims->psup->lpVtbl->Unsubscribe(pims->psup, pims->ulOQConn);
pims->ulOQConn = 0;
}

if (pims->ulTblConn)
{
pims->psup->lpVtbl->Unsubscribe(pims->psup, pims->ulTblConn);
pims->ulTblConn = 0;
}

OBJ_SetFlag(pims, MSF_BEINGDESTROYED);

ulFlags = pims->ulFlagsSLT;

hr = pims->psup->lpVtbl->StoreLogoffTransports(pims->psup, &ulFlags);

#ifdef DEBUG
/* The support object given us during config doesn't support */
/* StoreLogoffTransports. It isn't an error to get no support */
/* in that case. */
if (hr != hrSuccess && GetScode(hr) != MAPI_E_NO_SUPPORT)
TraceSz1("SMS: IMS_Neuter: StoreLogoffTransports(LOGOFF_ABORT) "
"returned unexpected error %s", SzDecodeScode(GetScode(hr)));
#endif

if (pims->cRef != 0 || pims->pobjHead != 0)
{
TraceSz("\n---------------------------------------------------------"
"-------");
TraceSz("SMS: The following objects were not released before Logoff:");
IMS_NeuterChildren((POBJ) pims, 0);
TraceSz("-----------------------------------------------------------"
"-----\n");
}

/* Free MS object's resources */
FreeNull(pims->szStorePath);
FreeNull(pims->szProps);
LMFree(&pims->lmr, pims->eidStore.lpb);
CloseRFS(pims->prfs);

if (pims->lptblOutgoing)
{
if (pims->cOutgoingViews)
{
TraceSz1("Sample MS: IMS_Neuter: Leaked outgoing queue table "
"(# views left = %08lX)\n", pims->cOutgoingViews);
pims->cOutgoingViews = 0;
}

UlRelease(pims->lptblOutgoing);
}


CloseIMsgSession(pims->pmsgsess);
pims->pmsgsess = NULL;

/* Make the logon object forget about us */
if (pims->pmsl)
pims->pmsl->pims = NULL;
}

/*
* HrOpenIMSPropsFileRetry
*
* Purpose:
* Open the IMsgStore properties docfile as an IMessage
* instance to get/set properties.
* This retries up to NUM_RETRIES times on MAPI_E_NO_ACCESS
*
* Arguments:
* szFile The file to open.
* plmr a pointer to the linked memory routines.
* psup a pointer to the MAPI support object.
* fModify TRUE means the caller wants read/write access.
* FALSE means read-only access.
* lppmsg Address of a location in which to return a
* pointer to the newly opened IMessage instance.
*
* Returns:
* HRESULT
*
* Side effects:
* None.
*
* Errors:
* IMessage on IStorage opening errors.
*/
HRESULT HrOpenIMSPropsFileRetry(LPMSGSESS pmsgsess, LPTSTR szFile, PLMR plmr,
LPMAPISUP psup, BOOL fModify, LPMESSAGE * lppmsg)
{
UINT iRetry; /* number of attempts to open */
HRESULT hr;

iRetry = 0;

while (TRUE)
{
hr = HrOpenIMsg(pmsgsess, szFile, plmr, psup, FALSE, fModify, TRUE,
lppmsg);

if (GetScode(hr) != MAPI_E_NO_ACCESS || ++iRetry >= NUM_RETRIES)
break;

Sleep(500);
}

#ifdef DEBUG
if (iRetry >= NUM_RETRIES)
TraceSz("HrOpenIMSPropsFileRetry: Failing open. Too many tries.");
#endif

DebugTraceResult(HrOpenIMSPropsFileRetry, hr);
return hr;
}

/*
* HrGetSMSStandardNotifKey
*
* Purpose
*
* return the notification key for standard notifications (everything
* except outgoing queue notifications).
* memory should be freed with FreeNull
*
* In order to call the MAPI registration function, we need to turn
* an EntryID into a "notification key" (something unique to an
* object in the store that will remain constant throughout this
* logon session). In the case of the Sample Store, we can just
* use the local file name of the object (folder or message).
* If the user is interested in notifications about all objects in the
* store, we use the store's uid for the key.
*
* Parameters
* pims pointer to the message store object
* peid the entryid of the object
* lppKey pointer to the location to return the key
*/
static HRESULT HrGetSMSStandardNotifKey(PIMS pims, PEID peid,
LPNOTIFKEY * lppKey)
{
HRESULT hr;
LPNOTIFKEY lpKey;
ULONG cb; /* number of bytes in the key */

if (peid)
{
LPMAPIUID lpuidEID = NULL;
LPTSTR szPathEID = NULL;
LPTSTR szFileEID = NULL;

hr = HrDeconstructEID(peid, &lpuidEID, &szPathEID, &szFileEID);
if (hr == hrSuccess)
{
cb = CbNewNOTIFKEY(Cbtszsize(szFileEID));
hr = HrAlloc(cb, (PPV) &lpKey);
}

if (hr == hrSuccess)
{
lstrcpy((LPTSTR) &lpKey->ab, szFileEID);
lpKey->cb = Cbtszsize(szFileEID);
}

FreeNull(lpuidEID);
FreeNull(szPathEID);
FreeNull(szFileEID);

if (hr != hrSuccess)
goto exit;
}
else
{
/* The caller is interested in notifications on all objects in the */
/* store. Generate a key from our uid. */

cb = CbNewNOTIFKEY(sizeof(MAPIUID));
hr = HrAlloc(cb, (PPV) &lpKey);
if (hr != hrSuccess)
goto exit;

GetResourceUID(pims, (MAPIUID *) &(lpKey->ab[0]));
lpKey->cb = sizeof(MAPIUID);
}

exit:
if (hr != hrSuccess)
FreeNull(lpKey);
else
*lppKey = lpKey;

DebugTraceResult(HrGetSMSStandardNotifKey, hr);
return hr;
}