MSPFLD.C

/* 
* M S P F L D . C
*
* Code that implements the object methods of IMAPIFolder.
*
* Copyright 1992-1995 Microsoft Corporation. All Rights Reserved.
*/

#include "msp.h"

#define FLD_ValidateParameters(pobj, intf, method, arglist) \
OBJ_ValidateParameters(pobj, intf, method, sizeof(IFLD), &vtblIFLD, arglist)

static BOOL IFLD_IsInvalid(PIFLD pifld);
static HRESULT HrCreateContTblMutex(HANDLE *phCTMutex);
static BOOL FFoldInSameStore(PIFLD pifld, PIFLD lpDestFld);
static BOOL FIsAncestor(PIFLD pifldAncestor, PIFLD pifldDescendent);
static HRESULT HrGetSortOrder(PIFLD pifld, LPSSortOrderSet *lppsSortOrder);
static HRESULT HrFillHierarchyTable(PIFLD pifld, LPTABLEDATA lptbl);
static HRESULT HrIsRead(PIFLD pifld, PEID peidMsg, BOOL *pfRead);
static HRESULT HrCreateFolder(PIFLD pifldParent, LPSTR szName, LPSTR szComment,
BOOL fOpenIfExists, PIFLD *ppifldNew, BOOL *pfCreated);
static HRESULT HrDeleteSubDirectory(PIFLD pifldParent, PEID peidToDelete,
ULONG ulFlags, BOOL fContentsOnly);
static HRESULT HrCopyContents(PIFLD pifldSrc, PIFLD pifldDst, ULONG ulFlags,
LPSPropTagArray ptagaExcl);
static BOOL FFolderExists(PEID peid, PIMS pims);
static HRESULT HrSetSubFolderProp(PIFLD pifld, BOOL fSubFolder);
static HRESULT HrSetOneROFolderProp(PIFLD pifld, LONG lValue, ULONG ulPT);
static HRESULT HrEIDFromDisplayName(LPMAPITABLE pmt, LPSTR szName, PLMR plmr,
PEID *ppeid);
static HRESULT HrCreateMessageList(PIFLD pifld, LPENTRYLIST *lppEntryList);
static void DestroyMessageList(PLMR plmr, LPENTRYLIST *lppEntryList);

#define NUM_RW_FOLDER_PROPS 2 /* number read write initial folder props */

#define IFLD_EnterCriticalSection(pifld) OBJ_EnterCriticalSection((POBJ)pifld)
#define IFLD_LeaveCriticalSection(pifld) OBJ_LeaveCriticalSection((POBJ)pifld)

CALLERRELEASE ViewRelease;

TCHAR szSubfolderPropFile[] = TEXT("subfoldr.prp");
TCHAR szAllFilesTemplate[] = TEXT("*.*");

/* properties excluded on a copy of folders' .prp file */
const static SizedSPropTagArray(13, sptaExclFldProps) =
{
13,
{
PR_CONTENT_COUNT,
PR_CONTENT_UNREAD,
PR_ENTRYID,
PR_INSTANCE_KEY,
PR_PARENT_ENTRYID,
PR_OBJECT_TYPE,
PR_STORE_ENTRYID,
PR_STORE_RECORD_KEY,
PR_RECORD_KEY,
PR_FOLDER_TYPE,
PR_SUBFOLDERS,
PR_MESSAGE_RECIPIENTS,
PR_MESSAGE_ATTACHMENTS
}
};

/* read only property proptags */
/* order must match order initialized on CreateFolderStorage */
#define NUM_RO_FOLDER_PROPS 11 /* number read only initial folder props */
const static SizedSPropTagArray(NUM_RO_FOLDER_PROPS, sptaReadOnly) =
{
NUM_RO_FOLDER_PROPS,
{
PR_OBJECT_TYPE,
PR_RECORD_KEY,
PR_FOLDER_TYPE,
PR_CONTENT_COUNT,
PR_CONTENT_UNREAD,
PR_STORE_ENTRYID,
PR_STORE_RECORD_KEY,
PR_SUBFOLDERS,
PR_ENTRYID,
PR_PARENT_ENTRYID,
PR_INSTANCE_KEY
}
};

/* read only property attributes */
const static SizedSPropAttrArray(NUM_RO_FOLDER_PROPS, spaReadOnly) =
{
NUM_RO_FOLDER_PROPS,
{
PROPATTR_READABLE,
PROPATTR_READABLE,
PROPATTR_READABLE,
PROPATTR_READABLE,
PROPATTR_READABLE,
PROPATTR_READABLE,
PROPATTR_READABLE,
PROPATTR_READABLE,
PROPATTR_READABLE,
PROPATTR_READABLE,
PROPATTR_READABLE
}
};

/* initial sort order set for a contents table */
static SizedSSortOrderSet(1, sSortOrderContentsDefault) =
{
1,
0,
0,
{
PR_INSTANCE_KEY, /* the index column */
TABLE_SORT_ASCEND
}
};

/* message status property array */
static SPropTagArray sptaMessageStatus =
{
1,
{
PR_MESSAGE_FLAGS
}
};

/* DISPATCH TABLE */
IFLD_Vtbl vtblIFLD =
{
(IFLD_QueryInterface_METHOD *) OBJ_QueryInterface,
(IFLD_AddRef_METHOD *) OBJ_AddRef,
IFLD_Release,
(IFLD_GetLastError_METHOD *) IMS_GetLastError,
(IFLD_SaveChanges_METHOD *) IMS_SaveChanges,
IFLD_GetProps,
IFLD_GetPropList,
IFLD_OpenProperty,
IFLD_SetProps,
IFLD_DeleteProps,
IFLD_CopyTo,
IFLD_CopyProps,
(IFLD_GetNamesFromIDs_METHOD *) IMS_GetNamesFromIDs,
(IFLD_GetIDsFromNames_METHOD *) IMS_GetIDsFromNames,
IFLD_GetContentsTable,
IFLD_GetHierarchyTable,
(IFLD_OpenEntry_METHOD *) IMS_OpenEntry,
IFLD_SetSearchCriteria,
IFLD_GetSearchCriteria,
IFLD_CreateMessage,
IFLD_CopyMessages,
IFLD_DeleteMessages,
IFLD_CreateFolder,
IFLD_CopyFolder,
IFLD_DeleteFolder,
IFLD_SetReadFlags,
IFLD_GetMessageStatus,
IFLD_SetMessageStatus,
IFLD_SaveContentsSort,
IFLD_EmptyFolder
};

/****************************************************
* Methods on IMAPIFolder *
*****************************************************/

/****************************************************************
Folder properties are all implemented by creating a file
(FOLDER.PRP) inside each folder and using message properties
on it.
*************************************************************/

/***************************************************************************
- IFLD_Release
-
* Purpose:
* Given a valid folder object, decrements the reference count, and if
* zero, NULLS out the lpVtbl. If no child objects of the folder remain
* open, and no contents or hierarchy tables remain open, the routine
* also destroys the object (see OBJ_Destroy for details of that action).
*
* This release method is very similar to the standard one found in
* mspobj.c. The only difference is that this one also checks for any
* open contents or hierarchy tables, and doesn't actually destroy
* the folder until they are gone. See ViewRelease() for the other side
* of this implementation.
*
* Parameters
* pifld The folder object to release
*
* Returns:
* ULONG: The value of the reference count on the whole object. When
* fully released, it is zero. This information can be used for
* diagnostic and testing purposes only. It CANNOT be used by
* shipping code. See the OLE 2.0 programmers reference for more
* details. Note that this function will also return zero when
* the identity of the object has been destroyed. Therefore,
* clients may not rely on zero having any special significance
* other than for debugging purposes.
*/
STDMETHODIMP_(ULONG) IFLD_Release(PIFLD pifld)
{
LONG cRef;
POBJ pobj;

if (IFLD_IsInvalid(pifld))
{
TraceSz1("SampleMS: IFLD_Release(pifld=%08lX): Folder is invalid and is "
"being ignored", pifld);
return (0);
}

IFLD_EnterCriticalSection(pifld);

pobj = (POBJ) pifld;

AssertSz(pobj->cRef > 0, "IFLD_Release(): Too many releases");

cRef = --pobj->cRef;

if (cRef == 0)
{
pobj->lpVtbl = 0; /* should prevent being called again. */

if ( pobj->pobjHead == 0
&& pifld->lptblContents == NULL
&& pifld->lptblHierarchy == NULL)
{
OBJ_Destroy(pobj); /* will leave the critical section */
return (0);
}
}

IFLD_LeaveCriticalSection(pifld);

return (cRef);
}

/***************************************************************************
- IFLD_GetProps
-
* Purpose:
* Returns in pcval and ppval the values of the properties
* in ptaga. If the latter is NULL, all properties in the folder
* are returned.
*
* Parameters
* pifld The folder object whose properties are requested
* ptaga Pointer to a counted array of property tags of
* properties requested
* ulFlags UNICODE / String8
* pcval Pointer to number of values returned
* ppval Pointer to a variable in which the address of the
* returned property values is placed
*
* Returns:
* HRESULT
*
*/
STDMETHODIMP IFLD_GetProps(PIFLD pifld, LPSPropTagArray ptaga, ULONG ulFlags,
ULONG *pcval, LPSPropValue *ppval)
{
LPMESSAGE lpmsg = NULL; /* open property message */
HRESULT hr = hrSuccess;

FLD_ValidateParameters(
pifld,
IMAPIProp,
GetProps,
(pifld,
ptaga,
ulFlags,
pcval,
ppval));

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

IFLD_EnterCriticalSection(pifld);

/* open the property message */
hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, FALSE, &lpmsg);
if (hr != hrSuccess)
goto exit;

/* Pass the call off to IMessage */
hr = lpmsg->lpVtbl->GetProps(lpmsg, ptaga, ulFlags, pcval, ppval);

UlRelease(lpmsg);

{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 internal properties. Note that this function takes as an */
/* argument the HRESULT from the previous IMessage::GetProps call. */
/* We aren't ignoring the error. */

hr = HrWrap_GetProps(hr, pifld->pims, pifld->cval, pifld->pval, pcval,
ppval, FALSE, (ptaga != NULL), (POBJ)pifld);

exit:
IFLD_LeaveCriticalSection(pifld);

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

return HrCheckHr(hr, IMAPIProp_GetProps);
}

/***************************************************************************
- IFLD_GetPropList
-
* Purpose:
* Returns in ptaga the list of all currently accessible properties
* of pifld.
*
* Parameters
* pifld The folder object whose properties are requested
* ulFlags UNICODE / String8
* pptaga Pointer to a counted array of property tags of
* properties requested
* Returns:
* hr
*
*/
STDMETHODIMP IFLD_GetPropList(PIFLD pifld, ULONG ulFlags, LPSPropTagArray *pptaga)
{
HRESULT hr;
LPMESSAGE lpmsg = NULL;

FLD_ValidateParameters(
pifld,
IMAPIProp,
GetPropList,
(pifld,
ulFlags,
pptaga));

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

IFLD_EnterCriticalSection(pifld);

/* open the property message */
hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, FALSE,
&lpmsg);

/* If the property message was successfully opened, then pass the */
/* call off to IMessage */

if (hr == hrSuccess)
hr = lpmsg->lpVtbl->GetPropList(lpmsg, ulFlags, pptaga);

UlRelease(lpmsg);

IFLD_LeaveCriticalSection(pifld);
DebugTraceResult(IFLD_GetPropList, hr);
return HrCheckHr(hr, IMAPIProp_GetPropList);
}

/***************************************************************************
- IFLD_OpenProperty
-
* Purpose:
* Returns in *lppUnk a pointer to a newly created interface for the
* propery of pifld in ulPropTag
* Not supported on folders.
*
* Parameters
* pifld the folder object whose property interface is requested
* ulPropTag Property tag for the desired property--only ID is used.
* lpiid Pointer to the GUID for the interface
* ulInterfaceOptions Specifies interface-specific behavior
* ulFlags MAPI_CREATE, MAPI_MODIFY, MAPI_DEFERRED_ERRORS
* lppUnk Pointer to the newly created interface pointer
*
* Returns:
* hr
*
*/
STDMETHODIMP IFLD_OpenProperty(PIFLD pifld, ULONG ulPropTag, LPCIID lpiid,
ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN *lppUnk)
{
SCODE sc;

FLD_ValidateParameters(
pifld,
IMAPIProp,
OpenProperty,
(pifld,
ulPropTag,
lpiid,
ulInterfaceOptions,
ulFlags,
lppUnk));

sc = MAPI_E_NO_SUPPORT;

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

/***************************************************************************
- IFLD_SetProps
-
* Purpose:
* Sets the properties in pval for pifld.
* lppProblems is a pointer to a structure of problems,
* NULL If there weren't any.
*
* Parameters
* pifld the folder object whose properties are to be set
* cValues count of properties to be set
* pval Pointer to a an array of property value structures.
* ppErr Pointer to address of a property problem structure
* to be returned.
*
* Returns:
* hr PROP_E_SECURITY_VIOLATION, PROP_E_CALL_FAILED,
* PROP_E_CALL_FAILED
*
*/
STDMETHODIMP IFLD_SetProps(PIFLD pifld, ULONG cValues, LPSPropValue pval,
LPSPropProblemArray *ppErr)
{
LPMESSAGE lpmsgNew = NULL; /* new instance of property message */
HRESULT hr;

FLD_ValidateParameters(
pifld,
IMAPIProp,
SetProps,
(pifld,
cValues,
pval,
ppErr));

IFLD_EnterCriticalSection(pifld);

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

/* open the property message */
hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims,
TRUE, &lpmsgNew);
if (hr != hrSuccess)
goto exit;

/* set props on the property message */
hr = lpmsgNew->lpVtbl->SetProps(lpmsgNew, cValues, pval, ppErr);
if (hr != hrSuccess)
goto exit;

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

/* Must release the message before trying to update the hierarchy */
/* table. Multiple opens on the internal message are not allowed by */
/* IStorage. */

UlRelease(lpmsgNew);
lpmsgNew = NULL;

/* If this isn't the root folder, get the parent entryid, and call */
/* ChangeTable so that any new properties are updated in its */
/* hierarchy table row. */

if (FIsRoot(pifld->peid) == FALSE)
{
PEID peidParent = NULL;
PIMS pims = pifld->pims;

hr = HrGetParentEID(&pims->lmr, pifld->peid, &peidParent);
if (hr == hrSuccess)
{
ChangeTable(pims, peidParent, pifld->peid, MAPI_FOLDER,
TABLE_ROW_MODIFIED, TRUE);
LMFree(&pims->lmr, peidParent);
}
else
{
TraceSz1("Sample MS: IFLD_SetProps: failed to change hierarchy "
"table. sc == %s\n", SzDecodeScode(GetScode(hr)));
hr = hrSuccess;
}
}

exit:
UlRelease(lpmsgNew);

IFLD_LeaveCriticalSection(pifld);
DebugTraceResult(IFLD_SetProps, hr);
return HrCheckHr(hr, IMAPIProp_SetProps);
}

/***************************************************************************
- IFLD_DeleteProps
-
* Purpose:
* Deletes the properties in ptaga for pifld. ppErr
* is a pointer to a structure of problems, NULL If there weren't any.
*
* Parameters
* pifld the folder object whose properties are to be deleted
* ptaga Pointer to a counted array of property tags of the
* properties to be deleted. Must not be NULL.
* ppErr Pointer to address of a property problem structure
* to be returned.
* Returns:
* hr PROP_E_SECURITY_VIOLATION, PROP_E_CALL_FAILED,
* PROP_E_CALL_FAILED
*/
STDMETHODIMP IFLD_DeleteProps(PIFLD pifld, LPSPropTagArray ptaga,
LPSPropProblemArray *ppErr)
{
LPMESSAGE lpmsgNew = NULL; /* new instance of property message */
HRESULT hr = hrSuccess;

FLD_ValidateParameters(
pifld,
IMAPIProp,
DeleteProps,
(pifld,
ptaga,
ppErr));

IFLD_EnterCriticalSection(pifld);

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

/* open the property message */
hr = HrOpenPropertyMessageRetry(pifld->peid,
pifld->pims, TRUE, &lpmsgNew);
if (hr != hrSuccess)
goto exit;

/* Pass the call off to IMessage */
hr = lpmsgNew->lpVtbl->DeleteProps(lpmsgNew, ptaga, ppErr);
if (hr != hrSuccess)
goto exit;

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

/* Must release the message before trying to update the hierarchy */
/* table. Multiple opens on the internal message are not allowed by */
/* IStorage. */

UlRelease(lpmsgNew);
lpmsgNew = NULL;

/* If this isn't the root folder, get the parent entryid, and call */
/* ChangeTable so that any new properties are updated in its */
/* hierarchy table row. */

if (FIsRoot(pifld->peid) == FALSE)
{
PEID peidParent = NULL;
PIMS pims = pifld->pims;

hr = HrGetParentEID(&pims->lmr, pifld->peid, &peidParent);
if (hr == hrSuccess)
{
ChangeTable(pims, peidParent, pifld->peid, MAPI_FOLDER,
TABLE_ROW_MODIFIED, TRUE);
LMFree(&pims->lmr, peidParent);
}
else
{
TraceSz1("Sample MS: IFLD_DeleteProps: failed to change hierarchy "
"table. sc == %s\n", SzDecodeScode(GetScode(hr)));
hr = hrSuccess;
}
}

exit:
UlRelease(lpmsgNew);

IFLD_LeaveCriticalSection(pifld);

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

/***************************************************************************
- IFLD_CopyTo
-
* Purpose:
* Copy the properties and/or contents of the current object
* to a destination object
*
* Parameters
* pifldSrc current object
* ciidExcl Count of excluded interfaces in rgiidExcl
* rgiidExcl Array of iid's specifying excluded interfaces
* ptagaExcl Pointer to a counted array of property tags of
* properties not to be copied, can be NULL
* ulUIParam Handle of parent window cast to ULONG, NULL if
* no dialog requested
* piidDst Interface ID of the interface of lpDestObj
* lpDestObj destination object
* ulFlags MAPI_MOVE, MAPI_NOREPLACE, MAPI_DIALOG, MAPI_DECLINE_OK
* pprba Pointer to address of a property problem
* structure to be returned.
*
* Returns:
* hr
*
* Side effects:
*
* Errors:
*/
STDMETHODIMP IFLD_CopyTo(PIFLD pifldSrc, ULONG ciidExcl, LPCIID rgiidExcl,
LPSPropTagArray ptagaExcl, ULONG ulUIParam, LPMAPIPROGRESS lpProgress,
LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlagsIn,
LPSPropProblemArray *pprba)
{
HRESULT hr = hrSuccess;
BOOL fIsIMapiProp = TRUE; /* TRUE if are to copy as imapiprop */
BOOL fIsIMAPIFolder = TRUE; /* TRUE if are to copy as IMAPIFolder */
SCODE sc = S_OK;
LPCIID piid;
LPCIID piidMax;
ULONG ulFlags;

FLD_ValidateParameters(
pifldSrc,
IMAPIProp,
CopyTo,
(pifldSrc,
ciidExcl,
rgiidExcl,
ptagaExcl,
ulUIParam,
lpProgress,
piidDst,
lpDestObj,
ulFlagsIn,
pprba));

IFLD_EnterCriticalSection(pifldSrc);

/* Turn off MAPI_DIALOG flag, since we don't support it. */
ulFlags = (ulFlagsIn & ~MAPI_DIALOG);

piid = rgiidExcl;
piidMax = piid + ciidExcl;

/* find the interface to copy : IMAPIPROP or IMAPIFOLDER */
while (piid < piidMax)
{
if (IsEqualIID(piid, &IID_IMAPIFolder))
fIsIMAPIFolder = FALSE;
else if (IsEqualIID(piid, (LPIID) &IID_IMAPIProp))
fIsIMapiProp = FALSE;
piid++;
}

/* make sure that the destination can support this */
if (fIsIMAPIFolder && !IsEqualIID(piidDst, (LPIID) &IID_IMAPIFolder))
{
LPMAPIFOLDER pfld = NULL;

hr = ((LPUNKNOWN) lpDestObj)->lpVtbl->QueryInterface(lpDestObj,
(LPIID) &IID_IMAPIFolder, (LPVOID *) &pfld);
if (hr != hrSuccess)
{
Assert(GetScode(hr) == MAPI_E_INTERFACE_NOT_SUPPORTED);
goto exit;
}
UlRelease(pfld);
}
else if (fIsIMapiProp &&
!(IsEqualIID(piidDst, (LPIID) &IID_IMAPIProp) ||
IsEqualIID(piidDst, (LPIID) &IID_IMAPIFolder)))
{
LPMAPIPROP pmp = NULL;

hr = ((LPUNKNOWN) lpDestObj)->lpVtbl->QueryInterface(lpDestObj,
(LPIID) &IID_IMAPIProp, (LPVOID *) &pmp);
if (hr != hrSuccess)
{
Assert(GetScode(hr) == MAPI_E_INTERFACE_NOT_SUPPORTED);
goto exit;
}
UlRelease(pmp);
}

if (!FFoldInSameStore(pifldSrc, (PIFLD) lpDestObj))
{
LPMAPISUP psup = pifldSrc->pims->psup;

/* leave critical section before calling back to MAPI */
IFLD_LeaveCriticalSection(pifldSrc);

/* call MAPI's CopyProps */
hr = psup->lpVtbl->DoCopyTo(psup, (LPIID) &IID_IMAPIFolder, (LPVOID) pifldSrc,
ciidExcl, rgiidExcl, ptagaExcl, ulUIParam,
lpProgress, piidDst, lpDestObj, ulFlagsIn, pprba);

DebugTraceResult(IFLD_CopyTo, hr);
return (HrCheckHr(hr, IMAPIProp_CopyTo));
}

/* copy them as folders */
if (fIsIMAPIFolder)
{
PIFLD pifldDst = (PIFLD) lpDestObj;

/* make sure we have write access */

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

if (!FContainsProp(PR_CONTAINER_HIERARCHY, ptagaExcl))
{
/* can not move or copy a folder to a decendant or itself */
if (FIsAncestor(pifldSrc, pifldDst))
hr = ResultFromScode(MAPI_E_NO_ACCESS);
}
else if (ulFlags & MAPI_MOVE)
{
/* Can't set MAPI_MOVE and also exclude subfolders. */
hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
}

if (hr != hrSuccess)
goto exit;

if (!(ulFlags & MAPI_NOREPLACE))
{
/* Empty the destination folder before beginning. */
hr = pifldDst->lpVtbl->EmptyFolder(pifldDst, 0, NULL, 0);
if (hr != hrSuccess)
goto exit;
}

/* copy the contents of the source folder to the destination folder */
hr = HrCopyContents(pifldSrc, pifldDst, ulFlags, ptagaExcl);
if (hr != hrSuccess)
goto exit;
}

/* copy the properties */
if (fIsIMapiProp || fIsIMAPIFolder)
{
LPMESSAGE lpmsg;
BOOL fModify = FALSE;

if (ulFlags & MAPI_MOVE)
fModify = TRUE;

hr = HrOpenPropertyMessageRetry(pifldSrc->peid,
pifldSrc->pims, fModify, &lpmsg);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->CopyTo(lpmsg, ciidExcl,
rgiidExcl, ptagaExcl, ulUIParam, lpProgress,
(LPIID) &IID_IMAPIProp, lpDestObj, ulFlags, pprba);

if (hr == hrSuccess && fModify)
lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);

UlRelease(lpmsg);
}

exit:
IFLD_LeaveCriticalSection(pifldSrc);

DebugTraceResult(IFLD_CopyTo, hr);
return HrCheckHr(hr, IMAPIProp_CopyTo);
}

/***************************************************************************
- IFLD_CopyProps
-
* Purpose:
* Copy the properties and/or contents of the current object
* to a destination object
*
* Parameters
* pifldSrc current object
* ptagaIncl Pointer to a counted array of property tags of
* properties to be copied
* ulUIParam Handle of parent window cast to ULONG, NULL if
* no dialog requested
* piidDst Interface ID of the interface of lpDestObj
* lpDestObj destination object
* ulFlags MAPI_MOVE, MAPI_NOREPLACE, MAPI_DIALOG, MAPI_DECLINE_OK
* pprba Pointer to address of a property problem
* structure to be returned.
*
* Returns:
* hr
*
* Side effects:
*
* Errors:
*/
STDMETHODIMP IFLD_CopyProps(PIFLD pifldSrc,
LPSPropTagArray ptagaIncl, ULONG ulUIParam, LPMAPIPROGRESS lpProgress,
LPCIID piidDst, LPVOID lpDestObj, ULONG ulFlagsIn,
LPSPropProblemArray *pprba)
{
HRESULT hr = hrSuccess;

#ifdef WHEN_WORKING
PIFLD pifldDst = NULL; /* lpDestObj if it's a folder */
BOOL fIsIMAPIFolder; /* TRUE if are to copy as IMAPIFolder */
SCODE sc = S_OK;
LPIID piid;
LPIID piidMax;
ULONG ulFlags;
#endif /* WHEN_WORKING */

FLD_ValidateParameters(
pifldSrc,
IMAPIProp,
CopyProps,
(pifldSrc,
ptagaIncl,
ulUIParam,
lpProgress,
piidDst,
lpDestObj,
ulFlagsIn,
pprba));

IFLD_EnterCriticalSection(pifldSrc);

#ifdef WHEN_WORKING
/* Turn off MAPI_DIALOG flag, since we don't support it. */
ulFlags = (ulFlagsIn & ~MAPI_DIALOG);

fIsIMAPIFolder = FContainsProp(PR_CONTAINER_HIERARCHY, ptagaIncl)
|| FContainsProp(PR_CONTAINER_CONTENTS, ptagaIncl);
/* make sure that the destination can support this */
if (fIsIMAPIFolder && !IsEqualIID(piidDst, (LPIID) &IID_IMAPIFolder))
{
LPMAPIFOLDER pfld = NULL;

hr = ((LPUNKNOWN) lpDestObj)->lpVtbl->QueryInterface(lpDestObj,
(LPIID) &IID_IMAPIFolder, (LPVOID *) &pfld);
if (hr != hrSuccess)
{
Assert(GetScode(hr) == MAPI_E_INTERFACE_NOT_SUPPORTED);
goto exit;
}
UlRelease(pfld);
}
else if (!(IsEqualIID(piidDst, (LPIID) &IID_IMAPIProp) ||
IsEqualIID(piidDst, (LPIID) &IID_IMAPIFolder)))
{
LPMAPIPROP pmp = NULL;

hr = ((LPUNKNOWN) lpDestObj)->lpVtbl->QueryInterface(lpDestObj,
(LPIID) &IID_IMAPIProp, (LPVOID *) &pmp);
if (hr != hrSuccess)
{
Assert(GetScode(hr) == MAPI_E_INTERFACE_NOT_SUPPORTED);
goto exit;
}
UlRelease(pmp);
}

if (!FFoldInSameStore(pifldSrc, (PIFLD) lpDestObj))
{
LPMAPISUP psup = pifldSrc->pims->psup;

/* leave critical section before calling back to MAPI */
IFLD_LeaveCriticalSection(pifldSrc);

/* call MAPI's CopyProps */
hr = psup->lpVtbl->DoCopyProps(psup, (LPIID) &IID_IMAPIFolder,
(LPVOID) pifldSrc, ptagaIncl, ulUIParam,
lpProgress, piidDst, lpDestObj, ulFlagsIn, pprba);

DebugTraceResult(IFLD_CopyProps, hr);
return (HrCheckHr(hr, IMAPIProp_CopyProps));
}

/* copy them as folders */
if (fIsIMAPIFolder)
{
PIFLD pifldDst = (PIFLD) lpDestObj;

/* make sure we have write access */

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

if (FContainsProp(PR_CONTAINER_HIERARCHY, ptagaIncl))
{
/* can not move or copy a folder to a decendant or itself */

if (FIsAncestor(pifldSrc, pifldDst)) 
hr = ResultFromScode(MAPI_E_NO_ACCESS);
}
else if (ulFlags & MAPI_MOVE)
{
/* Can't set MAPI_MOVE and also exclude subfolders. */
hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
}

if (hr != hrSuccess)
goto exit;

if (!(ulFlags & MAPI_NOREPLACE))
{
/* Empty the destination folder before beginning. */
hr = pifldDst->lpVtbl->EmptyFolder(pifldDst, 0, NULL, 0);
if (hr != hrSuccess)
goto exit;
}


if (FContainsProp(PR_CONTAINER_CONTENTS, ptagaIncl))
{
/* copy the contents of the source folder to the destination folder */
hr = HrCopyContents(pifldSrc, pifldDst, ulFlags, NULL);
}
if (hr != hrSuccess)
goto exit;
}

/* copy the properties */
/* //$ Rebuild ptagaIncl without Contents and Hierarchy */
{
LPMESSAGE lpmsg;
BOOL fModify = FALSE;

if (ulFlags & MAPI_MOVE)
fModify = TRUE;

hr = HrOpenPropertyMessageRetry(pifldSrc->peid,
pifldSrc->pims, fModify, &lpmsg);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->CopyProps(lpmsg,
ptagaIncl, ulUIParam, lpProgress,
(LPIID) &IID_IMAPIProp, lpDestObj, ulFlags, pprba);

if (hr == hrSuccess && fModify)
lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);

UlRelease(lpmsg);
}


exit:
IFLD_LeaveCriticalSection(pifldSrc);

#else

IFLD_LeaveCriticalSection(pifldSrc);

/* call MAPI's CopyProps */
hr = pifldSrc->pims->psup->lpVtbl->DoCopyProps(pifldSrc->pims->psup,
(LPIID) &IID_IMAPIFolder, (LPVOID) pifldSrc, ptagaIncl, ulUIParam,
lpProgress, piidDst, lpDestObj, ulFlagsIn, pprba);

#endif /* WHEN_WORKING */

DebugTraceResult(IFLD_CopyProps, hr);
return HrCheckHr(hr, IMAPIProp_CopyProps);
}

/***************************************************************************
- IFLD_GetContentsTable
-
* Purpose:
* Returns a table with the summary information about the messages
* in the folder
*
* Parameters
* pifld the folder object
* ulFlags MAPI_DEFERRED_ERRORS, MAPI_ASSOCIATED
* lppTable location to place the table object
*
* Returns:
*
* Side effects:
*
* Errors:
*/
STDMETHODIMP IFLD_GetContentsTable(PIFLD pifld, ULONG ulFlags,
LPMAPITABLE *lppTable)
{
HRESULT hr = hrSuccess;
SCODE sc;
HANDLE hFindFile = FAILED_SEARCH;
LPTSTR szMessageTemplate = NULL; /* template for a message name */
LPSSortOrderSet lpsSortOrder = NULL;
BOOL fTableCreated = FALSE;
BOOL fInMutex = FALSE;
PIMS pims;

FLD_ValidateParameters(
pifld,
IMAPIContainer,
GetContentsTable,
(pifld,
ulFlags,
lppTable));

#ifdef VALIDATE
if (ulFlags & MAPI_ASSOCIATED)
return ResultFromScode(MAPI_E_NO_SUPPORT);

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

IFLD_EnterCriticalSection(pifld);

pims = pifld->pims;

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

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

if (pifld->lptblContents == NULL)
{
PINST pinst = (PINST) PvGetInstanceGlobals();
PLMR plmr = &pifld->pims->lmr;

if (pinst == NULL)
{
hr = ResultFromScode(MAPI_E_CALL_FAILED);
goto exit;
}

/* create the data for the table */
sc = CreateTable((LPIID) &IID_IMAPITableData,
plmr->lpAllocBuf, plmr->lpAllocMore,
plmr->lpFreeBuf, pinst->lpmalloc, TBLTYPE_DYNAMIC,
PR_INSTANCE_KEY, (LPSPropTagArray) &sPropTagsContents,
&(pifld->lptblContents));

if (sc != S_OK)
{
hr = ResultFromScode(sc);
goto exit;
}
fTableCreated = TRUE;

/* Get the table mutex so that we can access the file without */
/* crossing paths with another process. */

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

hr = HrReadTableFromDisk(pifld->lptblContents, (POBJ) pifld,
pifld->peid->szPath, CONTENTS_COLUMNS, szContentsFileName);
if (hr != hrSuccess)
{
/* non-fatal. If the data on disk is missing or invalid, we */
/* should regenerate the data via HrSyncContentsTable. */

TraceSz1("SMS: Error %s reading table from disk. Regenerating "
"table on disk.", SzDecodeScode(GetScode(hr)));

hr = hrSuccess;
}
}

/* If we don't already have the mutex, get it so that we can access */
/* the file without crossing paths with another process. */

if (!fInMutex)
{
WaitForSingleObject(pims->hContTblMutex, INFINITE);
fInMutex = TRUE;
}

hr = HrSyncContentsTable(pifld, TRUE);

Assert(fInMutex);

ReleaseMutex(pims->hContTblMutex);
fInMutex = FALSE;

if (hr != hrSuccess)
goto exit;

/* open a view to the table */
/* get the sort order from the PR_SMS_CONTENTS_SORT_ORDER property */
hr = HrGetSortOrder(pifld, &lpsSortOrder);
if (hr != hrSuccess)
goto exit;

/* get the view */
hr = pifld->lptblContents->lpVtbl->HrGetView(pifld->lptblContents,
lpsSortOrder, ViewRelease, (ULONG) pifld, lppTable);
if (hr != hrSuccess)
goto exit;

/* record this view in pifld */
pifld->cContentsViews++;

exit:
if (fInMutex)
ReleaseMutex(pims->hContTblMutex);

/* free the sort order */
LMFree(&pifld->pims->lmr, lpsSortOrder);

/* remove the table if it is created in error */
if (hr != hrSuccess && fTableCreated)
{
UlRelease(pifld->lptblContents);
pifld->cContentsViews = 0;
pifld->lptblContents = NULL;
}

CloseIDSearch(&hFindFile, &szMessageTemplate);

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_GetContentsTable, hr);
return HrCheckHr(hr, IMAPIFolder_GetContentsTable);
}

/***************************************************************************
- IFLD_GetHierarchyTable
-
* Purpose:
* Returns a table with the summary information about the child
* folders of the current folder
*
* Parameters
* pifld the folder object
* ulFlags CONVENIENT_DEPTH if set indicates pre-order traversal
* MAPI_DEFERRED_ERRORS
* MAPI_UNICODE
* lppTable location to place the table object
*
* Returns:
*
* Side effects:
*
* Errors:
*/
STDMETHODIMP IFLD_GetHierarchyTable(PIFLD pifld, ULONG ulFlags,
LPMAPITABLE *lppTable)
{
HRESULT hr;
LPTABLEDATA lptad = NULL;

FLD_ValidateParameters(
pifld,
IMAPIContainer,
GetHierarchyTable,
(pifld,
ulFlags,
lppTable));

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

IFLD_EnterCriticalSection(pifld);

/* if table needs refreshing */
if (pifld->lptblHierarchy == NULL)
{
PINST pinst = (PINST) PvGetInstanceGlobals();
SCODE sc;
PLMR plmr = &pifld->pims->lmr;

pifld->cHierarchyViews = 0;

if (pinst == NULL)
{
hr = ResultFromScode(MAPI_E_CALL_FAILED);
goto exit;
}

/* create the table */
sc = CreateTable((LPIID) &IID_IMAPITableData,
plmr->lpAllocBuf, plmr->lpAllocMore,
plmr->lpFreeBuf, pinst->lpmalloc, TBLTYPE_DYNAMIC,
PR_INSTANCE_KEY, (LPSPropTagArray) &sPropTagsHierarchy, &lptad);

if (sc != S_OK)
{
hr = ResultFromScode(sc);
goto exit;
}

/* add a row or traversal for each subfolder of pifld */
/* Note that the sample store only supports a max depth of 1. */

hr = HrFillHierarchyTable(pifld, lptad);
if (hr != hrSuccess)
goto exit;

pifld->lptblHierarchy = lptad;
lptad = NULL;
}

/* open a view to the table */
hr = pifld->lptblHierarchy->lpVtbl->HrGetView(pifld->lptblHierarchy,
NULL, ViewRelease, (ULONG) pifld, lppTable);
if (hr != hrSuccess)
goto exit;

/* record this view in pifld */
pifld->cHierarchyViews++;

exit:
/* remove the table if it is created in error */
if (hr != hrSuccess && lptad)
UlRelease(lptad);

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_GetHierarchyTable, hr);
return HrCheckHr(hr, IMAPIFolder_GetHierarchyTable);
}

/***************************************************************************
- IFLD_CreateMessage
-
* Purpose:
* Creates a new message in the store
*
* Parameters
* pifld the folder object
* ulFlags flags. Valid flags to pass are MAPI_DEFERRED_ERRORS
* and MAPI_ASSOCIATED. The sample store doesn't support
* MAPI_ASSOCIATED, so the code returns no support if
* that flag is passed.
* piid reserved for future use
* lppMessage location to place the message object
*
* Returns:
*
* Side effects:
*
* Errors:
*/
STDMETHODIMP IFLD_CreateMessage(PIFLD pifld, LPCIID piid, ULONG ulFlags,
LPMESSAGE *lppMessage)
{
HRESULT hr;
PEID peid = NULL;
ULONG ulSeqNumber;
PIMSG pimsg = NULL;
LPTSTR szFull = NULL;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
CreateMessage,
(pifld,
piid,
ulFlags,
lppMessage));

#ifdef VALIDATE
if (ulFlags & MAPI_ASSOCIATED)
return ResultFromScode(MAPI_E_NO_SUPPORT);

if (piid && !FQueryInterface(OT_MESSAGE, piid))
return ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
#endif

IFLD_EnterCriticalSection(pifld);

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

/* create the message */

hr = HrNewEID(pifld, pifld->pims, TEMP_EXT, &ulSeqNumber, &peid);
if (hr != hrSuccess)
goto exit;

hr = HrNewIMSG(peid, pifld->pims, TRUE, TRUE, ulSeqNumber, &szFull, &pimsg);
if (hr != hrSuccess)
goto exit;

hr = InitIMSGProps(pimsg);
if (hr != hrSuccess)
goto exit;

/* folder contents counts and tables are not updated until message */
/* has changes saved */

exit:
LMFree(&pifld->pims->lmr, peid);

if (hr == hrSuccess)
*lppMessage = (LPMESSAGE) pimsg;
else if (pimsg)
{
UlRelease(pimsg);

Assert(szFull);
DeleteFile(szFull);
}

FreeNull(szFull);

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_CreateMessage, hr);
return HrCheckHr(hr, IMAPIFolder_CreateMessage);
}

/***************************************************************************
- IFLD_CopyMessages
-
* Purpose:
* Moves/Copies messages from the source folder to the destination folder
* EntryId's are not changed here since they get stamped in when the
* messages are opened.
*
* Parameters
* pifld the source folder
* lpMsgList the list of messages to be moved/copied
* piid the interface id of destination folder
* lpfolderDst the destination folder
* ulUIParam Handle of parent window,
* ignored if MESSAGE_DIALOG not set
* lpProgress points to progress dialog object
* ulFlags MAPI_DECLINE_OK, MESSAGE_MOVE, or MESSAGE_DIALOG
*/
STDMETHODIMP IFLD_CopyMessages(PIFLD pifld, LPENTRYLIST lpMsgList,
LPCIID piid, LPMAPIFOLDER lpfolderDst, ULONG ulUIParam,
LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
LONG cHandled = 0; /* number messages copied/moved */
LPTSTR szSrcName = NULL; /* name of file to be moved */
LPTSTR szDestName = NULL; /* name of the file after it is moved */
ULONG cbSrcPath = 0; /* length in bytes of path to src fld */
ULONG cbDestPath = 0; /* length in bytes of path to dest fld */
ULONG cbStorePath = 0; /* length in bytes of path to root of store */
TCHAR szBlanks[CCH_NAME] = TEXT(" . ");
HRESULT hr = hrSuccess;
PIFLD pifldDst;
BOOL fMove;
SBinary *psb;
SBinary *psbMax;
PIMS pims;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
CopyMessages,
(pifld,
lpMsgList,
piid,
lpfolderDst,
ulUIParam,
lpProgress,
ulFlags));

IFLD_EnterCriticalSection(pifld);

pifldDst = (PIFLD) lpfolderDst;
pims = pifld->pims;

/* if the folders aren't in the same store, call back to MAPI */

if (!FFoldInSameStore(pifld, pifldDst))
{
/* leave critical section before calling back to MAPI */
IFLD_LeaveCriticalSection(pifld);

return pims->psup->lpVtbl->CopyMessages(pims->psup,
(LPIID) &IID_IMAPIFolder, (LPVOID) pifld, lpMsgList, piid,
(LPVOID) lpfolderDst, ulUIParam, lpProgress, ulFlags);
}

fMove = !!(ulFlags & MESSAGE_MOVE);

if ( !OBJ_TestFlag(pifldDst, OBJF_MODIFY)
|| ( fMove
&& !OBJ_TestFlag(pifld, OBJF_MODIFY)))
{
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}

if (fMove)
{
/* If moving messages and the source and destination are the same */
/* folder, we are done even before we begin. */

ULONG ulTheSame;

hr = pims->lpVtbl->CompareEntryIDs(pims,
CbEID(pifldDst->peid), (LPENTRYID) pifldDst->peid,
CbEID(pifld->peid), (LPENTRYID) pifld->peid, 0L, &ulTheSame);
if (hr != hrSuccess)
goto exit;

if (ulTheSame)
{
hr = hrSuccess;
goto exit;
}
}

/* get storage for the path names */
cbStorePath = Cbtszsize(pims->szStorePath);
cbSrcPath = cbStorePath + CbEIDPath(pifld->peid);
if (FIsRoot(pifld->peid))
cbSrcPath--;

cbDestPath = cbStorePath + CbEIDPath(pifldDst->peid);
if (FIsRoot(pifldDst->peid))
cbDestPath--;

hr = HrFullPathName(pims->szStorePath, pifld->peid->szPath,
szBlanks, &szSrcName);
if (hr != hrSuccess)
goto exit;

hr = HrFullPathName(pims->szStorePath, pifldDst->peid->szPath,
szBlanks, &szDestName);
if (hr != hrSuccess)
goto exit;

psb = lpMsgList->lpbin;
psbMax = psb + lpMsgList->cValues;

/* move/copy each message */
while (psb < psbMax)
{
ULONG ulMF;
ULONG ulObjType;
BOOL fLocked;
BOOL fRead;
PIMSG pimsg;
LPSTR szRelativePath;
PEID peidNewID;
PEID peidToCopy;
ULONG cbEntryID;

cbEntryID = psb->cb;
peidToCopy = (PEID) (psb->lpb);

if ( FIsInvalidEID(cbEntryID, peidToCopy, pims)
|| !FIsMessage((PEID) peidToCopy))
{
hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
goto exit;
}

/* open the message */
hr = pifld->lpVtbl->OpenEntry(pifld, CbEID(peidToCopy),
(LPENTRYID) peidToCopy, NULL, 0L, &ulObjType,
(LPUNKNOWN *) &pimsg);
if (hr != hrSuccess)
goto exit;

Assert(ulObjType == MAPI_MESSAGE);

/* retrieve the PR_MESSAGE_FLAGS property */
hr = HrGetSingleProp((LPMAPIPROP) pimsg->lpmsg, &pims->lmr,
PR_MESSAGE_FLAGS, &ulMF);

UlRelease(pimsg);

if (hr != hrSuccess)
goto exit;

fLocked = !!(ulMF & MSGFLAG_SUBMIT);
fRead = !!(ulMF & MSGFLAG_READ);

/* if the message is locked then this is an error */
if (fLocked)
{
hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
goto exit;
}

/* store in szSrcName the root relative path of the message to be moved*/
lstrcpy(szSrcName + cbSrcPath, SzBaseName(peidToCopy));

/* store in szDestName the store relative path name of */
/* destination file */
if (fMove)
lstrcpy(szDestName + cbDestPath, SzBaseName(peidToCopy));
else
{
/* If we're copying, we need to generate a new filename for the */
/* message, or else we will have two messages with the same */
/* filename, and copying again (or moving back) will fail. */

PEID peidTemp = NULL;

hr = HrNewEID(pifld, pims, MESSAGE_EXT, NULL, &peidTemp);
if (hr != hrSuccess)
goto exit;

lstrcpy(szDestName + cbDestPath, SzBaseName(peidTemp));

LMFree(&pims->lmr, peidTemp);
}

/* either move or copy the message */
if (fMove)
{
if (!MoveFile(szSrcName, szDestName))
{
TraceSz1("MoveFile failed: OS error %08lX.", GetLastError());
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}

/* remove this message from contents tables */

(void) HrIncrementOneROProp(pifld, -1, PR_CONTENT_COUNT);

if (!fRead)
(void) HrIncrementOneROProp(pifld, -1, PR_CONTENT_UNREAD);

ChangeTable(pims, pifld->peid, peidToCopy, MAPI_MESSAGE,
TABLE_ROW_DELETED, TRUE);
}
else
{
if (!CopyFile(szSrcName, szDestName, TRUE))
{
TraceSz1("CopyFile failed: OS error %08lX.", GetLastError());
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}
}

/* get new entryid for this message */
hr = HrFullToRelative(szDestName, pims, &szRelativePath);
if (hr != hrSuccess)
goto exit;

hr = HrConstructEID(&(peidToCopy->uidResource), &pims->lmr,
szRelativePath, &peidNewID);

FreeNull(szRelativePath);

if (hr != hrSuccess)
goto exit;

/* If we're copying, then we just used a new name for the file. */
/* Note that PR_RECORD_KEY, PR_ENTRYID, PR_INSTANCE_KEY, and */
/* PR_PARENT_ENTRYID are now different, but since these are stored */
/* in the opened msg, everything should be fine. */

/* add a row to the contents table of the destination folder */
(void) HrIncrementOneROProp(pifldDst, 1, PR_CONTENT_COUNT);

if (!fRead)
(void) HrIncrementOneROProp(pifldDst, 1, PR_CONTENT_UNREAD);

ChangeTable(pims, pifldDst->peid, peidNewID, MAPI_MESSAGE,
TABLE_ROW_ADDED, TRUE);

LMFree(&pims->lmr, peidNewID);

cHandled++;
psb++;
}

exit:
FreeNull(szDestName);
FreeNull(szSrcName);

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_CopyMessages, hr);
return HrCheckHr(hr, IMAPIFolder_CopyMessages);
}

/***************************************************************************
- IFLD_DeleteMessages
-
* Purpose:
* Deletes from the folder pifld all messages in the list lpMsgList
*
* Parameters
* pifld the folder from which messages are to be deleted
* lpMsgList the list of messages to be deleted
* ulUIParam handle to main window, cast to ULONG
* lpProgress points to progress dialog information
* ulFlags MESSAGE_DIALOG
*
* Returns:
*
* Side effects:
*
* Errors:
*/
STDMETHODIMP IFLD_DeleteMessages(PIFLD pifld, LPENTRYLIST lpMsgList,
ULONG ulUIParam, LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
PEID peidToDelete = NULL; /* pointer to next eid to be deleted */
LONG cDeleted = 0; /* number messages deleted */
LONG cUnreadDeleted = 0; /* number of unread messages deleted */
LPTSTR szToDeleteName = NULL; /* name of the next message to be deleted */
HRESULT hr = hrSuccess;
LPTSTR szBlankName = TEXT(" . ");
ULONG cchPath; /* length of path to message */
BOOL fIsRead; /* TRUE if deleting a read message */
DWORD dwErr;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
DeleteMessages,
(pifld,
lpMsgList,
ulUIParam,
lpProgress,
ulFlags));

IFLD_EnterCriticalSection(pifld);

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

hr = HrFullPathName(pifld->pims->szStorePath, pifld->peid->szPath,
szBlankName, &szToDeleteName);
if (hr != hrSuccess)
goto exit;

cchPath = lstrlen(szToDeleteName) - lstrlen(szBlankName);

/* delete each message in lpMsgList up until the first error */
while ((ULONG) cDeleted < lpMsgList->cValues)
{
peidToDelete = (PEID) (lpMsgList->lpbin[cDeleted].lpb);

/* make sure that this is a message's eid */
if (!FIsMessage(peidToDelete))
{
hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
goto exit;
}

/* make sure the message is not submitted */
if (FIsSubmittedMessage(pifld->pims, peidToDelete))
{
hr = ResultFromScode(MAPI_E_SUBMITTED);
goto exit;
}

/* See if it has been read */
hr = HrIsRead(pifld, peidToDelete, &fIsRead);
if (hr != hrSuccess)
goto exit;

/* remove the message from disk */
lstrcpy(szToDeleteName + cchPath, SzBaseName(peidToDelete));
if (!DeleteFile(szToDeleteName))
{
dwErr = GetLastError();

if ((dwErr != ERROR_FILE_NOT_FOUND && dwErr != ERROR_PATH_NOT_FOUND)
|| lpMsgList->cValues == 1)
{
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}
}

/* remove this message from any open tables */
ChangeTable(pifld->pims, pifld->peid, peidToDelete, MAPI_MESSAGE,
TABLE_ROW_DELETED, TRUE);

if (!fIsRead)
cUnreadDeleted++;

cDeleted++;
}

exit:

FreeNull(szToDeleteName);

if (cDeleted)
(void) HrIncrementOneROProp(pifld, -cDeleted, PR_CONTENT_COUNT);
if (cUnreadDeleted)
(void) HrIncrementOneROProp(pifld, -cUnreadDeleted, PR_CONTENT_UNREAD);

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_DeleteMessages, hr);
return HrCheckHr(hr, IMAPIFolder_DeleteMessages);
}

/***************************************************************************
- IFLD_CreateFolder
-
* Purpose:
* Create a new folder within the message store
*
* Parameters
* pifld the parent folder of the newly created folder
* ulFldType type of folder to be created
* szFldName name of the new folder
* szComment comment string for the new folder
* piid Reserved; must be NULL.
* ulFlags MAPI_UNICODE and/or MAPI_DEFERRED_ERRORS
* lppfolder pointer to variable to receive new folder object
*
*/
STDMETHODIMP IFLD_CreateFolder(PIFLD pifld, ULONG ulFldType, LPSTR szFldName,
LPSTR szComment, LPCIID piid, ULONG ulFlags, LPMAPIFOLDER *lppfolder)
{
HRESULT hr;
PIFLD pifldNew = NULL;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
CreateFolder,
(pifld,
ulFldType,
szFldName,
szComment,
piid,
ulFlags,
lppfolder));

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

if (piid && !FQueryInterface(OT_FOLDER, piid))
return ResultFromScode(MAPI_E_INTERFACE_NOT_SUPPORTED);
#endif

/* Sample store can only create generic folders. */
if (ulFldType != FOLDER_GENERIC)
return ResultFromScode(MAPI_E_NO_SUPPORT);

IFLD_EnterCriticalSection(pifld);

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

hr = HrCreateFolder(pifld, szFldName, szComment,
!!(ulFlags & OPEN_IF_EXISTS), &pifldNew, NULL);

if (hr != hrSuccess)
UlRelease(pifldNew);
else
*lppfolder = (LPMAPIFOLDER) pifldNew;

exit:
IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_CreateFolder, hr);
return HrCheckHr(hr, IMAPIFolder_CreateFolder);
}

/***************************************************************************
- IFLD_CopyFolder
-
* Purpose:
* Copy my child folder to the specified parent
*
* Parameters
* pifld the parent folder of the newly created folder
* cbEntryID length of entry ID of folder to copy
* lpEntryID entry ID of folder to copy
* lpiid interface to the destination folder
* lpfolder pointer to destination object
* szNewName new name to give the copied folder, may be NULL
* ulUIParam Handle of parent window,
* ignored if MESSAGE_DIALOG not set
* lpProgress points to progress dialog information
* ulFlags control flags
*
*/
STDMETHODIMP IFLD_CopyFolder(PIFLD pifld, ULONG cbEntryID, LPENTRYID lpEntryID,
LPCIID piid, LPVOID lpfolder, LPSTR szNewName,
ULONG ulUIParam, LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
HRESULT hr;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
CopyFolder,
(pifld,
cbEntryID,
lpEntryID,
piid,
lpfolder,
szNewName,
ulUIParam,
lpProgress,
ulFlags));

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

/* For now, all we do is call back to mapi. */

hr = pifld->pims->psup->lpVtbl->CopyFolder(pifld->pims->psup,
(LPIID) &IID_IMAPIFolder, (LPVOID) pifld, cbEntryID, lpEntryID,
piid, lpfolder, szNewName, ulUIParam, lpProgress, ulFlags);

DebugTraceResult(IFLD_CopyFolder, hr);
return HrCheckHr(hr, IMAPIFolder_CopyFolder);
}

/***************************************************************************
- IFLD_DeleteFolder
-
* Purpose:
* Deletes a subfolder
*
* Parameters
* pifld the current folder
* cbEntryID byte count of lpEntryID
* lpEntryID entryID of subfolder to be deleted
* ulUIParam handle to main window cast to ULONG
* lpProgress pointer to progess dialog information
* ulFlags DEL_MESSAGES and/or DEL_FOLDERS
* and/or FOLDER_DIALOG
*
* Returns:
*
* Errors:
*/
STDMETHODIMP IFLD_DeleteFolder(PIFLD pifld, ULONG cbEntryID,
LPENTRYID lpEntryID, ULONG ulUIParam, LPMAPIPROGRESS lpProgress,
ULONG ulFlags)
{
HRESULT hr = hrSuccess;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
DeleteFolder,
(pifld,
cbEntryID,
lpEntryID,
ulUIParam,
lpProgress,
ulFlags));

IFLD_EnterCriticalSection(pifld);


if (FIsInvalidEID(cbEntryID, (PEID) lpEntryID, pifld->pims)
|| !FIsFolder((PEID) lpEntryID))
{
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

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

/* delete this subdirectory of the current directory */
hr = HrDeleteSubDirectory(pifld, (PEID) lpEntryID, ulFlags, FALSE);

exit:
IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_DeleteFolder, hr);
return HrCheckHr(hr, IMAPIFolder_DeleteFolder);
}

STDMETHODIMP IFLD_SetSearchCriteria(PIFLD pifld, LPSRestriction lpRestriction,
LPENTRYLIST lpentrylist, ULONG ulSearchFlags)
{
DebugTraceSc(IFLD_SetSearchCriteria, MAPI_E_NO_SUPPORT);
return ResultFromScode(MAPI_E_NO_SUPPORT);
}

STDMETHODIMP IFLD_GetSearchCriteria(PIFLD pifld, ULONG ulFlags,
LPSRestriction *lppRestriction, LPENTRYLIST *lppfolderList,
ULONG *lpulSearchState)
{
DebugTraceSc(IFLD_GetSearchCriteria, MAPI_E_NO_SUPPORT);
return ResultFromScode(MAPI_E_NO_SUPPORT);
}

/***************************************************************************
- IFLD_SetReadFlags
-
* Purpose:
* Sets the READ flag for each of the given messages.
*
* Parameters
* pifld the source folder
* lpMsgList the list of messages to be moved/copied
* ulUIParam Handle of parent window,
* ignored if MESSAGE_DIALOG not set
* lpProgress points to progress dialog object
* ulFlags SUPPRESS_RECEIPT, CLEAR_READ_FLAG
*/
STDMETHODIMP IFLD_SetReadFlags(PIFLD pifld, LPENTRYLIST lpMsgList,
ULONG ulUIParam, LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
HRESULT hr = hrSuccess;
LPENTRYLIST pelst = NULL;
BOOL fFreeList = FALSE;
SBinary *psb;
SBinary *psbMax;
BOOL fMultipleNotifs = TRUE;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
SetReadFlags,
(pifld,
lpMsgList,
ulUIParam,
lpProgress,
ulFlags));

IFLD_EnterCriticalSection(pifld);

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

pelst = lpMsgList;

if (pelst == NULL)
{
/* Create the message list if the caller didn't give us one. */

hr = HrCreateMessageList(pifld, &pelst);
if (hr != hrSuccess)
goto exit;

fFreeList = TRUE;
}

if (pelst->cValues > 5)
fMultipleNotifs = FALSE;

psb = pelst->lpbin;
psbMax = psb + pelst->cValues;

/* Call IMessage::SetReadFlag on each message */

while (psb < psbMax)
{
ULONG ulObjType;
PIMSG pimsg;
PEID peid;
ULONG cbEntryID;

cbEntryID = psb->cb;
peid = (PEID) (psb->lpb);

if ( FIsInvalidEID(cbEntryID, peid, pifld->pims)
|| !FIsMessage((PEID) peid))
{
hr = ResultFromScode(MAPI_E_INVALID_PARAMETER);
goto exit;
}

/* open the message */
hr = pifld->lpVtbl->OpenEntry(pifld, CbEID(peid),
(LPENTRYID) peid, NULL, 0L, &ulObjType,
(LPUNKNOWN *) &pimsg);
if (hr != hrSuccess)
goto exit;

Assert(ulObjType == MAPI_MESSAGE);

hr = pimsg->lpVtbl->SetReadFlag(pimsg, ulFlags);

if (hr == hrSuccess && fMultipleNotifs)
ChangeTable(pimsg->pims, pifld->peid, pimsg->peid, MAPI_MESSAGE,
TABLE_ROW_MODIFIED, TRUE);

UlRelease(pimsg);

if (hr != hrSuccess)
goto exit;

psb++;
}

/* If several messages changed, then just send one notification to */
/* re-sync the entire contents table on other processes. */

if (!fMultipleNotifs)
ChangeTable(pifld->pims, pifld->peid, NULL, MAPI_MESSAGE,
TABLE_CHANGED, TRUE);

exit:
if (fFreeList)
DestroyMessageList(&pifld->pims->lmr, &pelst);

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_SetReadFlags, hr);
return HrCheckHr(hr, IMAPIFolder_SetReadFlags);
}

/***************************************************************************
- GetMessageStatus
-
* Purpose:
* Retrieves the status associated with a message in a folder
*
* Parameters
* pifld the current folder
* cbEntryID byte count of lpEntryID
* lpEntryID entryID of the message
* ulFlags reserved for future use, must be 0
* lpulMessageStatus pointer to variable to receive the status
*
*/
STDMETHODIMP IFLD_GetMessageStatus(PIFLD pifld, ULONG cbEntryID,
LPENTRYID lpEntryID, ULONG ulFlags, ULONG *lpulMessageStatus)
{
LPMESSAGE lpmsgMsg = NULL; /* lpEntryID as an open message */
ULONG ulObjType; /* type of object opened */
HRESULT hr = hrSuccess;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
GetMessageStatus,
(pifld,
cbEntryID,
lpEntryID,
ulFlags,
lpulMessageStatus));

IFLD_EnterCriticalSection(pifld);

if (FIsInvalidEID(cbEntryID, (PEID) lpEntryID, pifld->pims)
|| !FIsMessage((PEID) lpEntryID))
{
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

/* open up lpEntryID */
hr = pifld->lpVtbl->OpenEntry(pifld, cbEntryID,
lpEntryID, NULL, 0L, &ulObjType, (LPUNKNOWN *) &lpmsgMsg);
if (hr != hrSuccess)
goto exit;

/* get the property */
hr = HrGetSingleProp((LPMAPIPROP) lpmsgMsg, &pifld->pims->lmr,
PR_MSG_STATUS, lpulMessageStatus);

if (hr != hrSuccess)
goto exit;

exit:
UlRelease(lpmsgMsg);

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_GetMessageStatus, hr);
return HrCheckHr(hr, IMAPIFolder_GetMessageStatus);
}

/***************************************************************************
- SetMessageStatus
-
* Purpose:
* Sets the 32-bit status associated with a message in a folder
*
* Parameters
* pifld the current folder
* cbEntryID byte count of lpentryID
* lpEntryID entryID of the message
* ulNewStatus new status to be assigned
* ulNewStatusMask mask applied to new status indicating bits to set
* lpulOldStatus pointer to variable to hold previous status
*
*/
STDMETHODIMP IFLD_SetMessageStatus(PIFLD pifld, ULONG cbEntryID,
LPENTRYID lpEntryID, ULONG ulNewStatus, ULONG ulNewStatusMask,
ULONG *lpulOldStatus)
{
PIMSG pimsg = NULL;
LPMESSAGE lpmsg;
ULONG ulObjType; /* type of object just opened */
HRESULT hr = hrSuccess;
ULONG ulStatus;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
SetMessageStatus,
(pifld,
cbEntryID,
lpEntryID,
ulNewStatus,
ulNewStatusMask,
lpulOldStatus));

IFLD_EnterCriticalSection(pifld);

if (FIsInvalidEID(cbEntryID, (PEID) lpEntryID, pifld->pims) ||
!FIsMessage((PEID) lpEntryID))
{
hr = ResultFromScode(MAPI_E_INVALID_ENTRYID);
goto exit;
}

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

/* open up lpEntryID */
hr = pifld->lpVtbl->OpenEntry(pifld, CbEID((PEID) lpEntryID),
lpEntryID, NULL, 0L, &ulObjType, (LPUNKNOWN *) &pimsg);
if (hr != hrSuccess)
goto exit;

lpmsg = pimsg->lpmsg;

/* get the old status */
hr = HrGetSingleProp((LPMAPIPROP) lpmsg, &pifld->pims->lmr,
PR_MSG_STATUS, &ulStatus);
if (hr != hrSuccess)
goto exit;

if (lpulOldStatus)
*lpulOldStatus = ulStatus;

if (ulNewStatusMask)
{
ulNewStatus &= ulNewStatusMask;
ulStatus &= ~ulNewStatusMask;
ulStatus |= ulNewStatus;

hr = HrSetOneROProp(lpmsg, &pifld->pims->lmr, PR_MSG_STATUS,
&ulStatus);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->SaveChanges(lpmsg, KEEP_OPEN_READWRITE);

UlRelease(pimsg);
pimsg = NULL;

if (hr != hrSuccess)
goto exit;

ChangeTable(pifld->pims, pifld->peid, (PEID) lpEntryID, MAPI_MESSAGE,
TABLE_ROW_MODIFIED, TRUE);
}

exit:
UlRelease(pimsg);

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_SetMessageStatus, hr);
return HrCheckHr(hr, IMAPIFolder_SetMessageStatus);
}

/***************************************************************************
- IFLD_SaveContentsSort
-
* Purpose:
* set default sort order for contents tables on pifld
*
* Parameters
* pifld the current folder
* lpSortCriteria points to the sort criteria
* ulFlags RECURSIVE_SORT
*
*/
STDMETHODIMP IFLD_SaveContentsSort(PIFLD pifld, LPSSortOrderSet lpSortCriteria,
ULONG ulFlags)
{
SPropValue pval; /* sort order property value */
LPSPropProblemArray pprba = NULL;
HRESULT hr = hrSuccess;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
SaveContentsSort,
(pifld,
lpSortCriteria,
ulFlags));

IFLD_EnterCriticalSection(pifld);

pval.Value.MVl.lpl = NULL;

/* store the sort order in pval */
pval.ulPropTag = PR_SMS_CONTENTS_SORT_ORDER;
pval.Value.MVl.cValues = 2 * lpSortCriteria->cSorts;
pval.Value.MVl.lpl = (LONG *) lpSortCriteria->aSort;

/* set the sort order property */
hr = pifld->lpVtbl->SetProps(pifld, 1, &pval, &pprba);

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

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_SaveContentsSort, hr);
return HrCheckHr(hr, IMAPIFolder_SaveContentsSort);
}

/*
* IFLD_EmptyFolder
*
* Purpose:
* This function empties a folder based on the flags given. It will never
* actually delete the folder itself, and will only empty the specified
* items.
*
* Parameters
* pifld The address of the folder object to empty.
* ulUIParam Handle of the parent window. (for progress). NYI
* lpProgress Address of progress dialog object
* ulFlags Control flags. Setting DEL_ASSOCIATED causes FAI
* to also be deleted. The FOLDER_DIALOG flag is NYI.
* DEL_ASSOCIATED has no meaning to the sample store.
*
* Returns:
*/
STDMETHODIMP IFLD_EmptyFolder(PIFLD pifld, ULONG ulUIParam,
LPMAPIPROGRESS lpProgress, ULONG ulFlags)
{
HRESULT hr;
PIFLD pifldParent = NULL;

FLD_ValidateParameters(
pifld,
IMAPIFolder,
EmptyFolder,
(pifld,
ulUIParam,
lpProgress,
ulFlags));

IFLD_EnterCriticalSection(pifld);

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

/* Empty the folder. */

hr = HrOpenParent(pifld->pims, pifld->peid, MAPI_MODIFY, &pifldParent);
if (hr != hrSuccess)
goto exit;

hr = HrDeleteSubDirectory(pifldParent, pifld->peid,
(DEL_MESSAGES | DEL_FOLDERS), TRUE);
if (hr != hrSuccess)
goto exit;

exit:
UlRelease(pifldParent);

IFLD_LeaveCriticalSection(pifld);

DebugTraceResult(IFLD_EmptyFolder, hr);
return HrCheckHr(hr, IMAPIFolder_EmptyFolder);
}

/****************************************************
* Private Functions *
****************************************************/

/*
* IFLD_IsInvalid
*
* Purpose check if pifld points to an invalid folder.
*
* Parameter
* pifld pointer to the folder
*
* Returns: BOOL - TRUE means the folder is invalid.
*/

static BOOL IFLD_IsInvalid(PIFLD pifld)
{
return (IsBadWritePtr(pifld, sizeof(IFLD)) ||
pifld->lpVtbl != &vtblIFLD);
}

/*
* HrCreateContTblMutex
*
* Purpose
* Create the contents table mutex, and return it to the caller.
*
* Arguments
* phCTMutex: Pointer to the location to return the new mutex.
*
* Returns:
* HRESULT: Will return an error only if the CreateMutex call fails.
*/
static HRESULT HrCreateContTblMutex(HANDLE *phCTMutex)
{
HRESULT hr = hrSuccess;
HANDLE hMutex;
LPTSTR szMutexName = "SMS_CONTTBLFILE_MUTEX";

hMutex = CreateMutex(NULL, FALSE, szMutexName);

if (hMutex)
*phCTMutex = hMutex;

#ifndef WIN16
else
{
TraceSz1("SampleMS: HrCreateContTblMutex: call to"
" CreateMutex failed (error %08lX)", GetLastError());

hr = ResultFromScode(MAPI_E_CALL_FAILED);
}
#endif

DebugTraceResult(HrCreateContTblMutex, hr);
return hr;
}

/*
* FFoldInSameStore
*
* Purpose
* Given that the source folder is a folder object within
* our store (i.e. it's a PIFLD, not LPMAPIFOLDER), check if the
* destination folder object is also within the same store.
*
* Parameters
* pifld: The folder that we already know is within this store.
* lpDestFld: The folder that we are checking. It may not be in this store.
*
* Returns: BOOL - TRUE means the folders are both in this store.
*
*/

static BOOL FFoldInSameStore(PIFLD pifld, PIFLD lpDestFld)
{
if (IsBadWritePtr(lpDestFld, sizeof(IFLD)))
return FALSE;

if (lpDestFld->lpVtbl != &vtblIFLD)
return FALSE;

if (pifld->pims != lpDestFld->pims)
return FALSE;

return TRUE;
}

/***************************************************************************
* IFLD_Neuter
*
* Purpose Free all memory allocated inside a folder object, and release
* any objects held inside a folder object. Leaves the object
* itself alone (the caller will free it).
*
* Argument pifld the folder to be cleaned out.
*
*/
void IFLD_Neuter(PIFLD pifld)
{
/* free allocated memory */
FreeNull(pifld->peid);

LMFree(&pifld->pims->lmr, pifld->pval);

/* remove any tables */
if (pifld->lptblContents)
{
TraceSz1("SampleMS (IFLD_Neuter): %d contents table views left",
pifld->cContentsViews);

UlRelease(pifld->lptblContents);
pifld->cContentsViews = 0;
pifld->lptblContents = NULL;
}

if (pifld->lptblHierarchy)
{
TraceSz1("SampleMS (IFLD_Neuter): %d hierarchy table views left",
pifld->cHierarchyViews);

UlRelease(pifld->lptblHierarchy);
pifld->cHierarchyViews = 0;
pifld->lptblHierarchy = NULL;
}

return;
}

/*
- ViewRelease
-
* 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
*
* Parameters
* 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)ViewRelease(ULONG ulCallerData, LPTABLEDATA lptbl,
LPMAPITABLE lpvtView)
{
PIFLD pifld; /* folder object for this view */
ULONG *pulViewsLeft; /* ptr to number of open views left */
LPTABLEDATA *pptbl; /* ptr to folder's internal table data */

pifld = (PIFLD) ulCallerData;

/* do nothing if the folder is invalid. Don't use IFLD_IsInvalid */
/* because the folder may have already been released, and we still */
/* want to release the view in that case. */
if (IsBadWritePtr(pifld, sizeof(IFLD)))
return;

IFLD_EnterCriticalSection(pifld);

/* find the kind of table this view is on */
if (pifld->lptblContents == lptbl)
{
pulViewsLeft = &(pifld->cContentsViews);
pptbl = &(pifld->lptblContents);
}
else if (pifld->lptblHierarchy == lptbl)
{
pulViewsLeft = &(pifld->cHierarchyViews);
pptbl = &(pifld->lptblHierarchy);
}
else
{
/* invalid table */
TrapSz("Invalid call to ViewRelease");
goto exit;
}

AssertSz(*pptbl == lptbl, "Different table data given to ViewRelease");

/* decrement count of views and release*/
/* the table data if the viewlist is empty */
if (--(*pulViewsLeft) == 0)
{
POBJ pobj = (POBJ) pifld;

UlRelease(lptbl);
*pptbl = NULL;

/* These tests are the same as IFLD_Release in this module. That is */
/* the release method that folders use. */

if ( pobj->cRef == 0
&& pobj->pobjHead == 0
&& pifld->lptblContents == NULL
&& pifld->lptblHierarchy == NULL)
{
OBJ_Destroy(pobj); /* will leave the critical section */
return;
}

}

exit:
IFLD_LeaveCriticalSection(pifld);

return;
}

/*
* HrIsRead
*
* Purpose
* Given a parent folder and the entryid of a message, determine
* whether the message has been read (i.e., whether the MSGFLAG_READ
* bit inside the PR_MESSAGE_FLAGS property is set).
*
* Parameters
* pifld A pointer to the parent folder object of the message.
* peidMsg The entryid of the message.
* pfRead A pointer to the location to return a BOOL saying
* whether or not the message was read. Set to TRUE
* if the message was read, FALSE otherwise.
*/
static HRESULT HrIsRead(PIFLD pifld, PEID peidMsg, BOOL *pfRead)
{
HRESULT hr;
PIMSG pimsg = NULL; /* opened version of lpidMsg */
ULONG ulObjType; /* type of object opened */
ULONG ulMF;

/* open the message */
hr = pifld->lpVtbl->OpenEntry(pifld, CbEID(peidMsg), (LPENTRYID) peidMsg,
NULL, 0L, &ulObjType, (LPUNKNOWN *) &pimsg);
if (hr != hrSuccess)
goto exit;

Assert(ulObjType == MAPI_MESSAGE);

/* retrieve the PR_MESSAGE_FLAGS property */
hr = HrGetSingleProp((LPMAPIPROP) pimsg->lpmsg, &pifld->pims->lmr,
PR_MESSAGE_FLAGS, &ulMF);

/* set pfRead: no PR_MESSAGE_FLAGS means unread */
/* The "!!" operator is used to change a bit that is set anywhere in a */
/* word to exactly TRUE (only one bit set at the far right of the word). */
/* The first "!" is NOT, which makes all zeros in the value into TRUE, */
/* and anything else into FALSE. The second "!" reverses that back, so */
/* that all zeros become FALSE, and anything else becomes TRUE. */

if (hr == hrSuccess)
*pfRead = !!(ulMF & MSGFLAG_READ);
else if (GetScode(hr) == MAPI_E_NOT_FOUND)
{
*pfRead = FALSE;
hr = hrSuccess;
}

UlRelease(pimsg);

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

DebugTraceResult(HrIsRead, hr);
return hr;
}

/*
* HrRemoveRow
*
* Purpose remove a row from a contents or hierarchy table
*
* Argument lptbl the table whose row is to be deleted
* peid the entryid of row to be deleted. Used to
* generate the instance key.
*
*/
HRESULT HrRemoveRow(LPTABLEDATA lptbl, PEID peid)
{
SPropValue valIK; /* instance key stored in a property value */
HRESULT hr;

if (lptbl == NULL)
return hrSuccess;

/* make the instance key into a property value */
valIK.ulPropTag = PR_INSTANCE_KEY;
valIK.Value.bin.cb = CbEID(peid);
valIK.Value.bin.lpb = (BYTE *) peid;

/* delete the row from the table */
hr = lptbl->lpVtbl->HrDeleteRow(lptbl, &valIK);

AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

DebugTraceResult(HrRemoveRow, hr);
return hr;
}

/*
* HrUpdateRow
*
* Purpose
* alter the row in the table with the given index property
* to the properties in the proptag array. If the index is
* not present, a new row is added to the table. The index
* property MUST be the first in pPTA.
* For contents table, an entry is made only if the object has
* been saved (and therefore has a PR_ENTRYID property).
*
* Parameters
* pims A pointer to the message store object.
* lptbl A pointer to the table data object to update.
* peid EntryID of object whose row is to be added
* pPTA array of property tags of properties to appear in
* this row
* pft pointer to the file time to write
* ulObjType MAPI_MESSAGE (for contents table) or MAPI_FOLDER
* (for hierarchy table)
*
*/
HRESULT HrUpdateRow(PIMS pims, LPTABLEDATA lptbl, PEID peid,
LPSPropTagArray pPTA, FILETIME *pft, ULONG ulObjType)
{
SRow srUpdateRow; /* new table row */
LPSPropValue pval = NULL;
LPMAPIPROP lpEntry = NULL; /* szName as an open mapiprop */
ULONG ulObjectType; /* type of object szName is */
HRESULT hr = hrSuccess;
ULONG cbEID; /* number of bytes in lpEntryID */
ULONG cValues;

if (lptbl == NULL)
return hrSuccess;

cbEID = CbEID(peid);

/* Don't add a contents table row for unsaved messages */
if (ulObjType == MAPI_MESSAGE && FIsUnsavedEID(peid))
goto exit;

/* open this object. Do not open with MAPI_MODIFY because if the */
/* store is open read-only, the OpenEntry will fail. */

hr = ((LPMDB) pims)->lpVtbl->OpenEntry((LPMDB) pims, cbEID,
(LPENTRYID) peid, NULL, 0L, &ulObjectType, (LPUNKNOWN *) &lpEntry);
if (hr != hrSuccess)
goto exit;

if (ulObjType == MAPI_MESSAGE && FIsUnsavedMsg((PIMSG) lpEntry))
{
UlRelease(lpEntry);
goto exit;
}

hr = lpEntry->lpVtbl->GetProps(lpEntry, pPTA, 0, /* ansi */
&cValues, &pval);

UlRelease(lpEntry);

if (HR_FAILED(hr))
goto exit;

hr = hrSuccess; /* ignore warnings */

Assert(cValues == pPTA->cValues);

/* add depth if this is a hierarchy table */
if (ulObjType == MAPI_FOLDER)
{
AssertSz(PROP_ID(pval[cValues - 1].ulPropTag) == PROP_ID(PR_DEPTH),
"Invalid assumption. PR_DEPTH must be last in hier ptags.");

pval[cValues - 1].ulPropTag = PR_DEPTH;
pval[cValues - 1].Value.l = 1L;
}

/* stamp in the last mod time (based on what's passed in) */
/* Note assumption of where the proptag is. */

AssertSz1(pPTA->aulPropTag[MODIFY_INDEX] == PR_LAST_MODIFICATION_TIME,
"Invalid assumption. PR_LAST_MODIFICATION_TIME must be at "
"index %d in pPTA.", MODIFY_INDEX);

pval[MODIFY_INDEX].ulPropTag = PR_LAST_MODIFICATION_TIME;
pval[MODIFY_INDEX].Value.ft = *pft;

/* add the row */
srUpdateRow.cValues = cValues;
srUpdateRow.lpProps = pval;

hr = lptbl->lpVtbl->HrModifyRow(lptbl, &srUpdateRow);

/* Fall through to exit */

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

LMFree(&pims->lmr, pval);

DebugTraceResult(HrUpdateRow, hr);
return hr;
}

/*
* HrCreateFolder
*
* This routine searches for a child folder of the given parent folder
* for a folder with the specified PR_DISPLAY_NAME. If found and the caller
* wishes to open if exists, then the found folder is opened and returned. If
* found and the caller doesn't want to open if exists, then the error
* MAPI_E_COLLISION is returned. If no child folder with the matching display
* name is found, then a child folder with the correct display name is created
* and the new folder is returned.
*
* Parameters
*
* pifldParent: The parent folder in which to search for matching display names,
* and, if necessary, to create the child folder.
* szName: The display name to search for and to create the child folder
* with.
* szComment: If a child folder is created, the value to set its PR_COMMENT
* property to.
* fOpenIfExists: If a child folder with a matching PR_DISPLAY_NAME is found,
* open the matching folder when this BOOL is TRUE. If this
* BOOL is FALSE, and a match is found, return MAPI_E_COLLISION.
* If no match is found, this parameter is not referenced.
* ppifldNew: The location to return the newly created or opened folder.
* pfCreated: The location to return whether this function created or opened
* the child folder. Returning TRUE means the folder was created.
*
* Errors: memory or disk errors, and MAPI_E_COLLISION.
*
*/
static HRESULT HrCreateFolder(PIFLD pifldParent, LPSTR szName, LPSTR szComment,
BOOL fOpenIfExists, PIFLD *ppifldNew, BOOL *pfCreated)
{
HRESULT hr;
BOOL fCreated = FALSE;
PEID peid = NULL;
PIFLD pifldNew = NULL;
LPMAPITABLE pmt = NULL;
PIMS pims = pifldParent->pims;
ULONG ulRowCount;
BOOL fFirstSubFld;

hr = pifldParent->lpVtbl->GetHierarchyTable(pifldParent, 0, &pmt);
if (hr != hrSuccess)
goto exit;

hr = pmt->lpVtbl->GetRowCount(pmt, 0, &ulRowCount);
if (hr != hrSuccess)
goto exit;

fFirstSubFld = (ulRowCount == 0);

if (!fFirstSubFld)
{
hr = HrEIDFromDisplayName(pmt, szName, &pims->lmr, &peid);

if (hr != hrSuccess)
goto exit;

/* If we found a duplicate, and we don't want it, we have an error. */

if ((peid != NULL) && (fOpenIfExists == FALSE))
{
hr = ResultFromScode(MAPI_E_COLLISION);
goto exit;
}
}

UlRelease(pmt);
pmt = NULL;

if (peid == NULL)
{
/* //$ Need to handle cleanup of directory and propfile when errors */
/* //$ occur later in this function... */

hr = HrCreateFolderStorage(pifldParent, FOLDER_GENERIC, szName,
szComment, TRUE, pims, &peid);

if (hr != hrSuccess)
goto exit;

fCreated = TRUE;
}

/* Create the instance data */
hr = HrNewIFLD(peid, pims, TRUE, &pifldNew);
if (hr != hrSuccess)
goto exit;

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

/* update the hierarchy tables, if any */
if (fCreated)
{
if (fFirstSubFld)
{
hr = HrSetSubFolderProp(pifldParent, TRUE);
if (hr != hrSuccess)
goto exit;
}

ChangeTable(pims, pifldParent->peid, peid, MAPI_FOLDER,
TABLE_ROW_ADDED, TRUE);
}

if (pfCreated)
*pfCreated = fCreated;

*ppifldNew = pifldNew;

exit:
UlRelease(pmt);
LMFree(&pims->lmr, peid);

if (hr != hrSuccess)
UlRelease(pifldNew);

DebugTraceResult(HrCreateFolder, hr);
return hr;
}

/*
* HrIsParent
*
* Purpose
* Checks if peidParent is the parent of peidChild. Note that peidChild
* may be either a message or a folder entryid. Returns answer
* (TRUE or FALSE) in *pfIsParent.
*
* Parameters
* peidParent A pointer to the entryid of the potential parent.
* peidChild A pointer to the entryid of the potential child.
* pfIsParent A pointer to the location to return the answer.
*/
HRESULT HrIsParent(PEID peidParent, PEID peidChild, BOOL *pfIsParent)
{
SCODE sc = S_OK;
LPSTR szLastSlash; /* pointer to last slash in Childs' path */
BOOL fAnswer = FALSE;

LPSTR szTemp = NULL; 
ULONG cbPathCopy;

if (!IsEqualMAPIUID(&(peidParent->uidResource), &(peidChild->uidResource)))
goto exit;

szLastSlash = SzFindLastCh(peidChild->szPath, '\\');
if (szLastSlash == NULL)
{
/* if child is the root it can have no parent */
/* otherwise, check if child lives in the root folder */

if (*(peidChild->szPath) != '\0')
fAnswer = (*(peidParent->szPath) == '\0');

goto exit;
}

cbPathCopy = ((szLastSlash - peidChild->szPath) * sizeof(TCHAR))
+ sizeof(TCHAR);

sc = ScAlloc(cbPathCopy, &szTemp);
if (sc != S_OK)
goto exit;

memcpy(szTemp, peidChild->szPath, (UINT) cbPathCopy);
szTemp[cbPathCopy - 1] = '\0';

/* see if paths are the same */
fAnswer = !lstrcmpi(peidParent->szPath, szTemp);

exit:
if (sc == S_OK)
*pfIsParent = fAnswer;

FreeNull(szTemp);

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

/*
* FIsAncestor
*
* Purpose
* returns TRUE if pifldDescendent is a descendent of pifldAncestor
*
* Parameters
* pifldAncestor the ancestor(?) folder
* pifldDescendent the descendent(?) folder
*
*/
static BOOL FIsAncestor(PIFLD pifldAncestor, PIFLD pifldDescendent)
{
PEID peidAncestor; /* entryID of pifldAncestor */
PEID peidDescendent; /* entryID of pifldDescendent */
LPTSTR szFirstOccurrence = NULL; /* pointer to first occurrence in */

/* pifldDescendent's path of */
/* pifldAncestor's path */

if (pifldAncestor == NULL || pifldDescendent == NULL)
return FALSE;

peidAncestor = pifldAncestor->peid;
peidDescendent = pifldDescendent->peid;

if (!IsEqualMAPIUID(&(peidAncestor->uidResource),
&(peidDescendent->uidResource)))
return FALSE;

/* check for either one being the root */
if (FIsRoot(peidAncestor))
return TRUE;
if (FIsRoot(peidDescendent))
return FALSE;

szFirstOccurrence = SzFindSz(peidDescendent->szPath,
peidAncestor->szPath);

return (BOOL) (szFirstOccurrence == peidDescendent->szPath);
}

/*
* HrFullToRelative
*
* Purpose given a full path name and store, returns the
* root relative path. Free with FreeNull.
*
* Parameters
* szFullPath the full path
* pims the store object
* pszRelative [out] pointer to the relative path
*
* NB memory is allocated using ScAlloc and should be freed
* with FreeNull
*/
HRESULT HrFullToRelative(LPTSTR szFullPath, PIMS pims, LPTSTR *pszRelative)
{
ULONG cchStore; /* length of the store path in tchars */
ULONG cchFull; /* length of the full path in tchars */
SCODE sc;

cchStore = lstrlen(pims->szStorePath);
cchFull = lstrlen(szFullPath);

Assert(cchFull >= cchStore);

sc = ScAlloc((cchFull - cchStore + sizeof(TCHAR)), (PPV) pszRelative);

if (sc != S_OK)
{
DebugTraceSc(HrFullToRelative, sc);
return ResultFromScode(sc);
}

lstrcpy(*pszRelative, szFullPath + cchStore + 1);

return hrSuccess;
}

/*
* HrFullPathName
*
* Purpose Allocate space for and return a pointer to the full path
* name of the given object. Free with FreeNull.
*
* Parameters
* szStorePath full path to the store root
* szPath1 first portion of path past the store root.
* May be NULL.
* szPath2 second portion of path to the object
* whose full path name is needed, may be NULL.
* pszName Pointer to a location to return the full pathname
*
* Returns:
* HRESULT
*/
HRESULT HrFullPathName(LPTSTR szStorePath, LPTSTR szPath1, LPTSTR szPath2,
LPTSTR *pszName)
{
UINT cbPath1 = 0; /* length in bytes of first portion of path */

/* excluding NULL */
UINT cbPath2 = 0; /* length in bytes of second portion of path */

/* excluding NULL */
UINT cbStorePath = 0;
SCODE sc;

Assert(!IsBadWritePtr(pszName, sizeof(LPSTR)));
Assert(!IsBadStringPtr(szStorePath, (UINT) -1));

Assert(szPath1 == NULL || !IsBadStringPtr(szPath1, (UINT) -1));
Assert(szPath2 == NULL || !IsBadStringPtr(szPath2, (UINT) -1));

cbStorePath = lstrlen(szStorePath) * sizeof(TCHAR);

if (szPath1 != NULL)
cbPath1 = lstrlen(szPath1) * sizeof(TCHAR);

if (szPath2 != NULL)
cbPath2 = lstrlen(szPath2) * sizeof(TCHAR);

/* allocate space for the path name */
sc = ScAlloc(cbStorePath + cbPath1 + cbPath2 + (3 * sizeof(TCHAR)),
(PPV) pszName);

if (sc != S_OK)
{
DebugTraceSc(HrFullPathName, sc);
return ResultFromScode(sc);
}

/* fill in the name */
lstrcpy(*pszName, szStorePath);

if (cbPath1 != 0)
{
lstrcat(*pszName, TEXT("\\"));
lstrcat(*pszName, szPath1);
}

if (cbPath2 != 0)
{
lstrcat(*pszName, TEXT("\\"));
lstrcat(*pszName, szPath2);
}

return hrSuccess;
}

/*
- HrNewEID
-
* Purpose Return the EID of a new object (folder or message)
*
* Parameters
* pifld Parent folder of object to receive new ID, NULL
* means we need EID of the root folder
* pims Store containing the new object
* szExtension File extension of new object, including '.'
* lpulSeqNumber pointer to sequence number of this entryID (optional)
* ppeid Pointer to pointer to new entryID
*
* Returns:
* HRESULT
*/
HRESULT HrNewEID(PIFLD pifld, PIMS pims, LPTSTR szExtension,
ULONG *lpulSeqNumber, PEID *ppeid)
{
PEID peidNew = NULL;
LPSTR szNewPath = NULL;
HRESULT hr = hrSuccess;
MAPIUID uid;
ULONG ulSeq;
LPSTR szNewObjName = NULL; /* name of new object */

Assert(ppeid);
Assert(pims);

/* get the new name of the object, and a unique sequence number. */
hr = HrUniqueFileName(pims, &ulSeq, &szNewObjName);
if (hr != hrSuccess)
goto exit;

/* code in the sample store depends on the number of chars in the */
/* file / folder name being OK. Verify that it is. */

Assert(lstrlen(szNewObjName) == CCH_BASE);

GetResourceUID(pims, &uid);

if (pifld == NULL)
{
CHAR ch = '\0';

/* Construct the EID for the root folder */
hr = HrConstructEID(&uid, &pims->lmr, (LPSTR) &ch, &peidNew);
if (hr != hrSuccess)
goto exit;
}
else
{
ULONG cbPath;
LPSTR szPath = NULL;
SCODE sc;
BOOL fIsRoot;

/* Add 1 TCHAR for the NULL terminator */
cbPath = (lstrlen(szNewObjName) + lstrlen(szExtension)) * sizeof(TCHAR)
+ sizeof(TCHAR);

fIsRoot = FIsRoot(pifld->peid);

if (fIsRoot == FALSE)
{
/* Add 1 TCHAR for the slash between the folder and the new name */
/* Subtract 1 TCHAR because we don't need to count the NULL term */

cbPath += CbEIDPath(pifld->peid) + sizeof(TCHAR) - sizeof(TCHAR);
}

/* allocate space for the new path */
sc = ScAlloc(cbPath, &szPath);
if (sc != S_OK)
{
hr = ResultFromScode(sc);
goto exit;
}

*szPath = 0;

if (fIsRoot == FALSE)
{
lstrcpy(szPath, pifld->peid->szPath);
lstrcat(szPath, TEXT("\\"));
}

lstrcat(szPath, szNewObjName);
lstrcat(szPath, szExtension);

hr = HrConstructEID(&uid, &pims->lmr, szPath, &peidNew);

FreeNull(szPath);

if (hr != hrSuccess)
goto exit;
}

*ppeid = peidNew;

if (lpulSeqNumber)
*lpulSeqNumber = ulSeq;

Assert(hr == hrSuccess);

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

if (hr != hrSuccess)
LMFree(&pims->lmr, peidNew);

FreeNull(szNewObjName);

DebugTraceResult(HrNewEID, hr);
return hr;
}

/*
* HrOpenPropertyMessage
*
* Purpose open the property message for the folder with entryid
* pointed to peid
*
* Parameters
* peid pointer to folder's entryid
* pims pointer to the message store object
* fModifyExclusive TRUE if we want to open for read/write,
* exclusive access.
* lppmsg pointer to location to return the open message
* object
*
* Returns: Storage and IMSG errors.
*/
static HRESULT HrOpenPropertyMessage(PEID peid, PIMS pims, BOOL fModifyExclusive,
LPMESSAGE *lppmsg)
{
LPTSTR szPropFolderPathName = NULL;
HRESULT hr;

/* get full path to message file */
hr = HrFullPathName(pims->szStorePath, peid->szPath, szPropertyFileName,
&szPropFolderPathName);
if (hr != hrSuccess)
goto exit;

hr = HrOpenIMsg(pims->pmsgsess, szPropFolderPathName, &pims->lmr,
pims->psup, FALSE, fModifyExclusive, fModifyExclusive, lppmsg);

if (hr != hrSuccess)
{
if (GetScode(hr) == MAPI_E_NOT_FOUND)
hr = ResultFromScode(MAPI_E_OBJECT_DELETED);
goto exit;
}

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

FreeNull(szPropFolderPathName);

DebugTraceResult(HrOpenPropertyMessage, hr);
return hr;
}

/*
* HrOpenPropertyMessageRetry
*
* Purpose open the property message for the folder with entryid
* pointed to peid. The function will retry if message is busy
* for the specified number of times.
*
* Parameters
* peid pointer to folder's eid
* pims pointer to the message store object
* fModifyExclusive TRUE if we want to open for read/write,
* exclusive access.
* lppmsg pointer to location to return the open message
* object
*
*/
HRESULT HrOpenPropertyMessageRetry(PEID peid, PIMS pims,
BOOL fModifyExclusive, LPMESSAGE *lppmsg)
{
UINT iRetry; /* number of attempts to open */
HRESULT hr = hrSuccess;

iRetry = 0;
while (TRUE)
{
hr = HrOpenPropertyMessage(peid, pims, fModifyExclusive, lppmsg);

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

Sleep(500);
}

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

DebugTraceResult(HrOpenPropertyMessageRetry, hr);
return hr;
}

/*
* HrGetFileModTime
*
* Gets the last write time of the file from the OS by calling
* FindFirstFile, and retrieving it from the FIND_DATA.
*
* szStorePath: the path to the message store.
* szFileName: the relative path to the file.
* pft: a pointer to the location to return the FILETIME
*
*/
HRESULT HrGetFileModTime(LPTSTR szStorePath, LPTSTR szFileName,
FILETIME *pft)
{
HRESULT hr;
LPSTR szFullPath;

hr = HrFullPathName(szStorePath, szFileName, NULL, &szFullPath);

if (hr == hrSuccess)
{
HANDLE hFile;
WIN32_FIND_DATA ffd;

hFile = FindFirstFile(szFullPath, &ffd);

if (hFile == FAILED_SEARCH)
hr = ResultFromScode(MAPI_E_NOT_FOUND);
else
{
*pft = ffd.ftLastWriteTime;
FindClose(hFile);
}

FreeNull(szFullPath);
}

DebugTraceResult(HrGetFileModTime, hr);
return hr;
}

/*
* HrFindFirstID
*
* Purpose returns in *ppeid the entryid of the
* first object in pifldParent whose name is of the
* form specified in szTemplate. The caller must free
* storage with CloseIDSearch even in error,
* except *ppeid is always freed with LMFree.
*
* Parameters
* pifldParent parent folder
* szTemplate template local name for object to be found
* pichLocal pointer to variable for position of start of local
* name of the found object
* pszRRPath pointer to root relative path of next object
* phFindFile pointer to location to return search handle
* pffd pointer to location to return Windows find file data
* struct for use by HrFindNextID.
* ppeid pointer to eid of found object
*
* Returns:
* HRESULT
*/
HRESULT HrFindFirstID(PIFLD pifldParent, LPTSTR szTemplate, ULONG *pichLocal,
LPTSTR *pszRRPath, HANDLE *phFindFile, WIN32_FIND_DATA *pffd, PEID *ppeid)
{
HRESULT hr = hrSuccess;
MAPIUID uidStore; /* uid for message store */
LPTSTR szSubObject = NULL; /* template name of subobject */
PEID peid = NULL;
PLMR plmr;
LPSTR szRRPath = NULL;
ULONG ichLocal = 0;
HANDLE hFindFile = 0;
WIN32_FIND_DATA ffd;

plmr = &pifldParent->pims->lmr;

/* find the first subobject */
hr = HrFullPathName(pifldParent->pims->szStorePath,
pifldParent->peid->szPath, szTemplate, &szSubObject);
if (hr != hrSuccess)
goto exit;

hFindFile = FindFirstFile(szSubObject, &ffd);

if (hFindFile == FAILED_SEARCH)
{
hr = ResultFromScode(MAPI_E_NOT_FOUND);
goto exit;
}

/* get components of entryids for messages in this folder */
GetResourceUID(pifldParent->pims, &uidStore);

/* get the root relative path */
hr = HrAlloc(CbEIDPath(pifldParent->peid)
+ (CCH_NAME * sizeof(TCHAR)), (PPV) &szRRPath);
if (hr != hrSuccess)
goto exit;

lstrcpy(szRRPath, pifldParent->peid->szPath);
if (*szRRPath != (TCHAR) 0)
lstrcat(szRRPath, TEXT("\\"));

ichLocal = lstrlen(szRRPath);

lstrcpy(szRRPath + ichLocal, ffd.cFileName);

/* construct the ID */
hr = HrConstructEID(&uidStore, plmr, szRRPath, &peid);
/* fall through to exit */

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

if (hr != hrSuccess)
{
LMFree(plmr, peid);
FreeNull(szRRPath);

if (hFindFile && hFindFile != FAILED_SEARCH)
FindClose(hFindFile);
}
else
{
*ppeid = peid;
*pszRRPath = szRRPath;
*phFindFile = hFindFile;
*pichLocal = ichLocal;
*pffd = ffd;
}

FreeNull(szSubObject);

#ifdef DEBUG
if (GetScode(hr) != MAPI_E_NOT_FOUND)
DebugTraceResult(HrFindFirstID, hr);
#endif

return hr;
}

/*
* HrFindNextID
*
* Purpose returns in ppeid the eid of the next object in the search
* free *ppeid with LMFree.
*
* Parameters
* pifldParent parent folder
* ichLocal position of start of local name of the found object
* szRRPath name of found object from last search
* hFindFile search handle
* pffd [in/out] pointer to location to use and return
* Windows find file data struct.
* ppeid pointer to location to return eid of next file.
*
* Returns:
* HRESULT
*/
HRESULT HrFindNextID(PIFLD pifldParent, ULONG ichLocal, LPTSTR szRRPath,
HANDLE hFindFile, WIN32_FIND_DATA *lpFindFileData, PEID *ppeid)
{
HRESULT hr = hrSuccess;
MAPIUID uidStore; /* resource uid of store */
PLMR plmr;
PEID peid = NULL;

plmr = &pifldParent->pims->lmr;

if (FindNextFile(hFindFile, lpFindFileData))
{
lstrcpy(szRRPath + ichLocal, lpFindFileData->cFileName);
GetResourceUID(pifldParent->pims, &uidStore);

/* construct the ID */
hr = HrConstructEID(&uidStore, plmr, szRRPath, &peid);
}
else
hr = ResultFromScode(MAPI_E_NOT_FOUND);

AssertSz(hr == hrSuccess || HR_FAILED(hr), "No warning expected");

if (hr != hrSuccess)
LMFree(plmr, peid);
else
*ppeid = peid;

#ifdef DEBUG
if (GetScode(hr) != MAPI_E_NOT_FOUND)
DebugTraceResult(HrFindNextID, hr);
#endif

return hr;
}

/*
* CloseIDSearch
*
* Purpose
* Calls FindClose on the search handle, and FreeNull on the root-relative
* pathname given as arguments. After cleaning up these values, the
* function also invalidates the pointers.
*
* Parameters
* lphFindFile pointer to search handle, returned with FAILED_SEARCH.
* pszRRPath pointer to name of found object from last search,
* returned with NULL.
*
* Returns: void.
*/
void CloseIDSearch(HANDLE *lphFindFile, LPTSTR *pszRRPath)
{
if (lphFindFile && *lphFindFile && *lphFindFile != FAILED_SEARCH)
{
FindClose(*lphFindFile);
*lphFindFile = FAILED_SEARCH;
}

FreeNull(*pszRRPath);
*pszRRPath = NULL;
}

/*
* HrNewIFLD
*
* Purpose:
* Allocates and initializes new instance data for a folder
*
* Parameters
* peid EID of folder
* pims address of the message store for this folder
* fModify TRUE if caller wants to modify, FALSE otherwise
* ppifld pointer to newly created instance data
*
* Returns:
* HRESULT
*
* Side effects:
* Allocates memory for IFLD object. Freed by calling Release.
*
*/
HRESULT HrNewIFLD(PEID peid, PIMS pims, BOOL fModify, PIFLD *ppifld)
{
SCODE sc;
PIFLD pifld = NULL; /* pointer to new instance data */

Assert(peid);
Assert(pims);
Assert(ppifld);

/* Don't open the folder object unless the directory and the property */
/* file are present on disk. */

if (!FFolderExists(peid, pims))
{
sc = MAPI_E_NOT_FOUND;
goto exit;
}

/* Allocate memory for the instance data */
sc = LMAllocZ(&pims->lmr, sizeof(IFLD), &pifld);
if (sc != S_OK)
goto exit;

/* allocate memory to hold the folder's eid */
sc = ScAllocZ(CbEID(peid), &pifld->peid);
if (sc != S_OK)
goto exit;

Assert(CbEIDPath(peid) == (UINT) Cbtszsize(peid->szPath));

OBJ_Initialize(pifld, &vtblIFLD, OT_FOLDER, pims, pims->pcs);

memcpy(pifld->peid, peid, CbEID(peid));

if (fModify)
OBJ_SetFlag(pifld, OBJF_MODIFY);

OBJ_Enqueue((POBJ) pifld, (POBJ) pims);

*ppifld = pifld;

exit:
AssertSz(sc == S_OK || FAILED(sc), "No warning expected");

if (sc != S_OK && pifld)
{
FreeNull(pifld->peid);
LMFree(&pims->lmr, pifld);
}

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

/*
* FFolderExists
*
* Check for the existence of the folder's property file inside the folder
* passed in. If the file isn't there or we can't get to it, return FALSE.
* If the file is there, return TRUE.
*
* Parameters:
* peid -- The entryid of the folder to test.
* pims -- a pointer to the message store object.
*
* Returns: TRUE if the folder's property file is found.
* FALSE in all other cases.
*
*/
BOOL FFolderExists(PEID peid, PIMS pims)
{
LPTSTR szPropFolderPathName = NULL;
HRESULT hr;
BOOL fFolderValid = FALSE;

/* get full path to message file */
hr = HrFullPathName(pims->szStorePath, peid->szPath, szPropertyFileName,
&szPropFolderPathName);

if (hr == hrSuccess)
{
HANDLE hFile;
WIN32_FIND_DATA ffd;

hFile = FindFirstFile(szPropFolderPathName, &ffd);

if (hFile != FAILED_SEARCH)
{
fFolderValid = TRUE; /* it's there! */
FindClose(hFile);
}

FreeNull(szPropFolderPathName);
}

return fFolderValid;
}

/*
- HrCreateFolderStorage
-
* Purpose:
* Create a new folder directory and its property file
*
* Parameters
* pifld the parent folder of the newly created folder
* If NULL, the new folder is the root folder.
* ulFolderType type of folder to be created
* szFolderName name of the new folder
* szFolderComment comment string for the new folder
* fCreateDir TRUE means create directory for folder
* pims pointer to store in which new folder resides
* ppeid pointer to entryID of new folder
*
* Returns:
*
* Side effects: Sets PR_COMMENT property to szFolderComment.
*
* Errors:
*
*/
HRESULT HrCreateFolderStorage(PIFLD pifld, ULONG ulFolderType,
LPSTR szFolderName, LPSTR szFolderComment, BOOL fCreateDir,
PIMS pims, PEID *ppeid)
{
HRESULT hr;
PEID peid = NULL;
ULONG ulSeqNumber; /* sequence number of new folder*/
SPropValue pvalRO[NUM_RO_FOLDER_PROPS];
SPropValue pvalRW[NUM_RW_FOLDER_PROPS];
ULONG cSet;
LPSPropProblemArray pprba = NULL;
LPMESSAGE lpmsgProp = NULL; /* property message for this folder */
BOOL fDirCreated = FALSE;
BOOL fFolderPropFileCreated = FALSE;

LPTSTR szNewDirectoryName = NULL;
LPTSTR szPropertyFolderName = NULL;

Assert(pims);
Assert(ppeid);

hr = HrNewEID(pifld, pims, FOLDER_EXT, &ulSeqNumber, &peid);
if (hr != hrSuccess)
goto exit;

/* create new directory for the new folder object */
hr = HrFullPathName(pims->szStorePath, peid->szPath, NULL,
&szNewDirectoryName);
if (hr != hrSuccess)
goto exit;

if (fCreateDir)
{
if (!CreateDirectory(szNewDirectoryName, NULL))
{
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}
fDirCreated = TRUE;
}

hr = HrFullPathName(pims->szStorePath, peid->szPath,
szPropertyFileName, &szPropertyFolderName);
if (hr != hrSuccess)
goto exit;

/* create and open the message object for properites */
hr = HrOpenIMsg(pims->pmsgsess, szPropertyFolderName, &pims->lmr,
pims->psup, TRUE, TRUE, TRUE, &lpmsgProp);

if (hr != hrSuccess)
goto exit;

fFolderPropFileCreated = TRUE;

/* set the initial read only properties */

pvalRO[0].ulPropTag = sptaReadOnly.aulPropTag[0];
pvalRO[1].ulPropTag = sptaReadOnly.aulPropTag[1];
pvalRO[2].ulPropTag = sptaReadOnly.aulPropTag[2];
pvalRO[3].ulPropTag = sptaReadOnly.aulPropTag[3];
pvalRO[4].ulPropTag = sptaReadOnly.aulPropTag[4];
pvalRO[5].ulPropTag = sptaReadOnly.aulPropTag[5];
pvalRO[6].ulPropTag = sptaReadOnly.aulPropTag[6];
pvalRO[7].ulPropTag = sptaReadOnly.aulPropTag[7];
pvalRO[8].ulPropTag = sptaReadOnly.aulPropTag[8];
pvalRO[9].ulPropTag = sptaReadOnly.aulPropTag[9];
pvalRO[10].ulPropTag = sptaReadOnly.aulPropTag[10];

pvalRO[0].Value.l = MAPI_FOLDER;
pvalRO[1].Value.bin.cb = sizeof(ULONG);
pvalRO[1].Value.bin.lpb = (BYTE *) &ulSeqNumber;
pvalRO[2].Value.l = (pifld == NULL) ? FOLDER_ROOT : FOLDER_GENERIC;
pvalRO[3].Value.l = 0;
pvalRO[4].Value.l = 0;

pvalRO[5].Value.bin.cb = pims->eidStore.cb;
pvalRO[5].Value.bin.lpb = pims->eidStore.lpb;

pvalRO[6].Value.bin.cb = sizeof(pims->uidResource);
pvalRO[6].Value.bin.lpb = (LPBYTE) &pims->uidResource;

pvalRO[7].Value.b = FALSE;

/* Set PR_ENTRYID, PR_PARENT_ENTRYID and PR_INSTANCE_KEY to null */
/* strings to keep clients from writing over them. */
/* We get the actual values internally. */

pvalRO[8].Value.bin.cb = 1;
pvalRO[8].Value.bin.lpb = (LPBYTE) "";

pvalRO[9].Value.bin.cb = 1;
pvalRO[9].Value.bin.lpb = (LPBYTE) "";

pvalRO[10].Value.bin.cb = 1;
pvalRO[10].Value.bin.lpb = (LPBYTE) "";

/* set these read only props */
hr = lpmsgProp->lpVtbl->SetProps(lpmsgProp, NUM_RO_FOLDER_PROPS,
pvalRO, &pprba);
if (hr != hrSuccess || pprba)
goto exit;

Assert(sptaReadOnly.cValues == NUM_RO_FOLDER_PROPS);
Assert(spaReadOnly.cValues == NUM_RO_FOLDER_PROPS);

hr = SetAttribIMsgOnIStg(lpmsgProp, (LPSPropTagArray) &sptaReadOnly,
(LPSPropAttrArray) &spaReadOnly, &pprba);
if (hr != hrSuccess || pprba)
goto exit;

/* set the read write properties */

cSet = NUM_RW_FOLDER_PROPS;

pvalRW[0].ulPropTag = PR_DISPLAY_NAME;

/* must be last since it is optional */
pvalRW[1].ulPropTag = PR_COMMENT;

pvalRW[0].Value.LPSZ = szFolderName;
if (szFolderComment != NULL)
pvalRW[1].Value.LPSZ = szFolderComment;
else
cSet--;

/* set the ReadWrite properties */

hr = lpmsgProp->lpVtbl->SetProps(lpmsgProp, cSet, pvalRW, &pprba);
if (hr != hrSuccess || pprba)
goto exit;

hr = lpmsgProp->lpVtbl->SaveChanges(lpmsgProp, 0);

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

UlRelease(lpmsgProp);

/* free the property problem structure */
if (pprba)
{
LMFree(&pims->lmr, pprba);
hr = ResultFromScode(MAPI_E_CALL_FAILED);
}

if (hr != hrSuccess)
{
LMFree(&pims->lmr, peid);

if (fFolderPropFileCreated == TRUE)
DeleteFile(szPropertyFolderName);

if (fDirCreated == TRUE)
RemoveDirectory(szNewDirectoryName);
}
else
*ppeid = peid;

FreeNull(szNewDirectoryName);
FreeNull(szPropertyFolderName);

DebugTraceResult(HrCreateFolderStorage, hr);
return hr;
}

/*
* HrIncrementOneROProp
*
* Purpose increment the read only property by delta. Only works on
* properties of type PT_LONG.
*
* Argument pifld pointer to the folder object
* lDelta size of increment
* ulPT the property tag to be changed
*/
HRESULT HrIncrementOneROProp(PIFLD pifld, LONG lDelta, ULONG ulPT)
{
LONG lValue;
HRESULT hr;
LPMESSAGE lpmsg = NULL; /* property message for pifld */
PLMR plmr = &pifld->pims->lmr;

AssertSz1(PROP_TYPE(ulPT) == PT_LONG,
"Trying to increment property %s; not PT_LONG",
SzDecodeUlPropTag(ulPT));

/* open the property message exclusively */
hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims,
TRUE, &lpmsg);
if (hr != hrSuccess)
goto exit;

/* get the current value of the properties */

hr = HrGetSingleProp((LPMAPIPROP) lpmsg, plmr, ulPT, &lValue);
if (hr != hrSuccess)
goto exit;

lValue += lDelta;

/* reset the new value */

hr = HrSetOneROProp(lpmsg, plmr, ulPT, &lValue);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->SaveChanges(lpmsg, FORCE_SAVE);

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

UlRelease(lpmsg);

DebugTraceResult(HrIncrementOneROProp, hr);
return hr;
}

/*
* HrSetOneROFolderProp
*
* Purpose set the read only folder property to the value given. The
* property must be of type PT_LONG.
*
* Argument pifld pointer to the folder object
* lValue value to set the property to.
* ulPT the property tag to be changed
*/
static HRESULT HrSetOneROFolderProp(PIFLD pifld, LONG lValue, ULONG ulPT)
{
HRESULT hr;
LPMESSAGE lpmsg = NULL; /* property message for pifld */
PLMR plmr = &pifld->pims->lmr;

AssertSz1(PROP_TYPE(ulPT) == PT_LONG,
"Trying to increment property %s; not PT_LONG",
SzDecodeUlPropTag(ulPT));

/* open the property message exclusively */
hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims,
TRUE, &lpmsg);
if (hr != hrSuccess)
goto exit;

/* reset the new value */

hr = HrSetOneROProp(lpmsg, plmr, ulPT, &lValue);
if (hr != hrSuccess)
goto exit;

hr = lpmsg->lpVtbl->SaveChanges(lpmsg, FORCE_SAVE);

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


UlRelease(lpmsg);

DebugTraceResult(HrSetOneROFolderProp, hr);
return hr;
}

/*
* HrSetSubFolderProp
*
* Purpose set the PR_SUBFOLDERS property
*
* Argument pifld pointer to the folder object
* fSubFolder the value to write into the PR_SUBFOLDER property
*/
static HRESULT HrSetSubFolderProp(PIFLD pifld, BOOL fSubFolder)
{
HRESULT hr;
LPMESSAGE lpmsg = NULL; /* property message for pifld */
PIFLD pifldParent = NULL;
PLMR plmr = &pifld->pims->lmr;
PEID peid = pifld->peid;

/* open the property message exclusively */
hr = HrOpenPropertyMessageRetry(peid, pifld->pims, TRUE, &lpmsg);
if (hr != hrSuccess)
goto exit;

/* set the new value */

hr = HrSetOneROProp(lpmsg, plmr, PR_SUBFOLDERS, &fSubFolder);
if (hr != hrSuccess)
goto exit;

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

UlRelease(lpmsg);
lpmsg = NULL;

/* If this isn't the root folder, get the parent entryid, and call */
/* ChangeTable so that any new properties are updated in its */
/* hierarchy table row. */

if (FIsRoot(peid) == FALSE)
{
PEID peidParent = NULL;
PIMS pims = pifld->pims;

hr = HrGetParentEID(&pims->lmr, pifld->peid, &peidParent);
if (hr == hrSuccess)
{
ChangeTable(pims, peidParent, peid, MAPI_FOLDER,
TABLE_ROW_MODIFIED, TRUE);
LMFree(&pims->lmr, peidParent);
}
else
{
TraceSz1("Sample MS: HrSetSubFolderProp: failed to change "
"hierarchy table. sc == %s\n", SzDecodeScode(GetScode(hr)));
hr = hrSuccess;
}
}

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

UlRelease(lpmsg);
UlRelease(pifldParent);

DebugTraceResult(HrSetSubFolderProp, hr);
return hr;
}

/*
* ChangeTable
*
* Purpose
* Changes all contents or hierarchy tables in all open folders to reflect
* the change specified. Also updates the folder's content and unread counts.
* Never updates the table on disk. That will happen when the table is
* read-in and verified.
*
* Parameters
*
* pims Pointer to the message store object.
* peidTable EID of the parent folder of the table to change
* peidObject the object that has been added or deleted or modified.
* May be NULL if ulTableEvent is TABLE_CHANGED. This
* means multiple items changed.
* ulObjType MAPI_MESSAGE (for contents tables) or MAPI_FOLDER
* (for hierarchy tables)
* ulTableEvent either TABLE_ROW_ADDED, DELETED, MODIFIED, or
* TABLE_CHANGED (TABLE_CHANGED for contents tables only)
* fSendNotif TRUE if this routine should send notifications to
* other processes about this change.
*
*/
void ChangeTable(PIMS pims, PEID peidTable, PEID peidObject,
ULONG ulObjType, ULONG ulTableEvent, BOOL fSendNotif)
{
HRESULT hr = hrSuccess;
ULONG cbEIDTable;
LPSPropTagArray pPTA; /* proptags of column headings */
POBJ pobj;

Assert(pims);
Assert(peidTable);
AssertSz1(ulTableEvent == TABLE_ROW_ADDED
|| ulTableEvent == TABLE_ROW_MODIFIED
|| ulTableEvent == TABLE_ROW_DELETED
|| ulTableEvent == TABLE_CHANGED, "Bad ulTableEvent %08lX",
ulTableEvent);
AssertSz1(ulObjType == MAPI_MESSAGE || ulObjType == MAPI_FOLDER,
"Bad ulObjType %08lX", ulObjType);

/*
* Look for all open tables within this process. We find them
* by search the open object chain for folders whose entryids match
* the eid of the folder passed in. We only update open tables.
* Therefore, don't bother checking objects that aren't folders or
* folder objects without open tables.
*/
cbEIDTable = CbEID(peidTable);

for (pobj = pims->pobjHead; pobj != NULL; pobj = pobj->pobjNext)
{
PIFLD pifld;
ULONG ulTheSame;
LPTABLEDATA lptbl;
FILETIME ft;

if (pobj->wType != OT_FOLDER)
continue;

pifld = (PIFLD) pobj;

if (ulObjType == MAPI_MESSAGE)
{
pPTA = (LPSPropTagArray) &sPropTagsContents;
lptbl = pifld->lptblContents;
}
else
{
pPTA = (LPSPropTagArray) &sPropTagsHierarchy;
lptbl = pifld->lptblHierarchy;
}

if (lptbl == NULL)
continue;

hr = pims->lpVtbl->CompareEntryIDs(pims, cbEIDTable,
(LPENTRYID) peidTable, CbEID(pifld->peid),
(LPENTRYID) pifld->peid, 0L, &ulTheSame);
if (hr != hrSuccess)
goto exit;

if (!ulTheSame)
continue;

switch (ulTableEvent)
{
case TABLE_CHANGED:
if (ulObjType != MAPI_MESSAGE)
{
TrapSz("ChangeTable doesn't handle TABLE_CHANGED on"
" hierarchy tables");
goto exit;
}

/* Don't update the file on disk. */
hr = HrSyncContentsTable(pifld, FALSE);
if (hr != hrSuccess)
goto exit;
break;

case TABLE_ROW_DELETED:
hr = HrRemoveRow(lptbl, peidObject);
if (hr != hrSuccess)
{
if (GetScode(hr) == MAPI_E_NOT_FOUND)
hr = hrSuccess;
else
goto exit;
}
break;

case TABLE_ROW_ADDED:
case TABLE_ROW_MODIFIED:
hr = HrGetFileModTime(pims->szStorePath, peidObject->szPath, &ft);
if (hr != hrSuccess)
goto exit;

hr = HrUpdateRow(pims, lptbl, peidObject, pPTA, &ft, ulObjType);
if (hr != hrSuccess)
goto exit;
break;

default:
/* We've already asserted this above. */
goto exit;
}
}

if (fSendNotif)
hr = HrSendNotif(pims, peidTable, peidObject, ulTableEvent, ulObjType);

exit:
if (hr != hrSuccess)
TraceSz1("SampleMS: ChangeTable: failed to update "
"table. Error %s.\n", SzDecodeScode(GetScode(hr)));

return;
}

/*
* HrDestroyFolderStorage
*
* Purpose Removes storage associated with a folder
*
* Parameters
*
* szFullPath full path name of the folder to be removed
*
*/
HRESULT HrDestroyFolderStorage(LPTSTR szFullPath)
{
HRESULT hr;
LPTSTR szAll = NULL; /* path to all files in the folder */
HANDLE hFile;
WIN32_FIND_DATA ffd;

/* Find all files in the directory, and attempt to delete them. */
/* Note that if somehow a subdirectory was created in this directory, */
/* this function will fail. The loop below (that deletes files) goes */
/* ahead regardless of error until it has tried to delete all files. */
/* The only fatal error occurs if we can't remove the directory itself. */

hr = HrAppendPath(szFullPath, szAllFilesTemplate, &szAll);
if (hr != hrSuccess)
goto exit;

hFile = FindFirstFile(szAll, &ffd);

if (hFile != INVALID_HANDLE_VALUE)
{
while (TRUE)
{
LPTSTR szTemp = NULL;

/* Don't even attempt to delete directories. The directories */
/* named "." and ".." are always included in the contents of */
/* a directory listing, and this avoids the attempt to delete */
/* them. */

if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
if (HrAppendPath(szFullPath, ffd.cFileName, &szTemp)
== hrSuccess)
{
DeleteFile(szTemp);
FreeNull(szTemp);
}
}

if (!FindNextFile(hFile, &ffd))
{
FindClose(hFile);
break;
}
}
}

/* Attempt to delete the folder itself. */
if (!RemoveDirectory(szFullPath))
hr = ResultFromScode(MAPI_E_NO_ACCESS);

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

FreeNull(szAll);

DebugTraceResult(HrDestroyFolderStorage, hr);
return hr;
}

/*
* HrFillHierarchyTable
*
* Purpose Construct table data for hierarchy below the folder pifld
*
* Argument
* pifld pointer to the folder to be added to
* the hierarchy table.
* lptbl underlying table data
*
*/
static HRESULT HrFillHierarchyTable(PIFLD pifld, LPTABLEDATA lptbl)
{
HRESULT hr;
PEID peidSub = NULL;
LPTSTR szSubFolder = NULL; /* template for subfolder of szFolder */
HANDLE hFindFile = FAILED_SEARCH;
ULONG ichLocal;
WIN32_FIND_DATA ffd;
PIMS pims = pifld->pims;

/* build a hierarchy entry for each subfolder */
hr = HrFindFirstID(pifld, szFolderTemplate, &ichLocal,
&szSubFolder, &hFindFile, &ffd, &peidSub);

while (hr == hrSuccess)
{
/* build hierarchy entry for this subfolder */
hr = HrUpdateRow(pims, lptbl, peidSub,
(LPSPropTagArray) &sPropTagsHierarchy,
&(ffd.ftLastWriteTime), MAPI_FOLDER);
if (hr != hrSuccess)
goto exit;

LMFree(&pims->lmr, peidSub);
peidSub = NULL;

hr = HrFindNextID(pifld, ichLocal, szSubFolder,
hFindFile, &ffd, &peidSub);
}

if (GetScode(hr) == MAPI_E_NOT_FOUND)
hr = hrSuccess;

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

/* close the search */
CloseIDSearch(&hFindFile, &szSubFolder);
LMFree(&pims->lmr, peidSub);

DebugTraceResult(HrFillHierarchyTable, hr);
return hr;
}

/*
* HrDuplicateIFLD
*
* Searches the existing subfolders of pifldParent for a folder with
* PR_DISPLAY_NAME of szName. If it finds a match, opens it and returns
* the matching folder (doing nothing else). If no folder matches, then
* the routine creates a new folder and copies all properties from the
* old folder into the new folder.
*
* pifld the old folder
* pifldParent parent of the new folder
* szName name of new folder
* szComment comment for new folder
* ppifldNew pointer to the location to return the new folder
*
*/
static HRESULT HrDuplicateIFLD(PIFLD pifld, PIFLD pifldParent, LPTSTR szName,
LPTSTR szComment, PIFLD *ppifldNew)
{
HRESULT hr = hrSuccess;
BOOL fCreated = FALSE;
PIFLD pifldNew = NULL;

/* create new folder */

hr = HrCreateFolder(pifldParent, szName, szComment, TRUE, &pifldNew,
&fCreated);
if (hr != hrSuccess)
goto exit;

/* copy pifld's properties over except excluded stuff (see */
/* sptaExclFldProps definition). */

if (fCreated)
{
LPSPropProblemArray pprba = NULL;
LPMESSAGE lpmsgPropSrc = NULL; /* pifld's property message */
LPMESSAGE lpmsgPropNew = NULL; /* new ifld's property message */

hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, FALSE,
&lpmsgPropSrc);

if (hr == hrSuccess)
hr = HrOpenPropertyMessageRetry(pifldNew->peid,
pifldNew->pims, TRUE, &lpmsgPropNew);

if (hr == hrSuccess)
hr = lpmsgPropSrc->lpVtbl->CopyTo(lpmsgPropSrc, 0, NULL,
(LPSPropTagArray) &sptaExclFldProps, 0, NULL,
(LPIID) &IID_IMessage, lpmsgPropNew, 0L, &pprba);

UlRelease(lpmsgPropSrc);
UlRelease(lpmsgPropNew);

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

if (hr != hrSuccess)
goto exit;

*ppifldNew = pifldNew;

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

DebugTraceResult(HrDuplicateIFLD, hr);
return hr;
}

/*
- HrGetSortOrder
-
* Purpose:
* returns in *lppsSortOrder the order of contents tables.
* Caller must use MAPIFreeBuffer to release *lppsSortOrder.
*
* Parameters
* pifld folder whose contents table is referred to
* lppsSortOrder pointer to sort order variable
*
* Returns:
* HRESULT
*
*/
static HRESULT HrGetSortOrder(PIFLD pifld, LPSSortOrderSet *lppsSortOrder)
{
ULONG cValues; /* number of property values returned */
LPSPropValue pval = NULL; /* returned property array */
HRESULT hr;
ULONG cbSos; /* number of bytes in sort order */
SCODE sc;
static SizedSPropTagArray(1, sptaSortOrder) =
{
1,
{
PR_SMS_CONTENTS_SORT_ORDER
}
};

Assert(pifld);
Assert(lppsSortOrder);

/* get the sort order property */
hr = pifld->lpVtbl->GetProps(pifld,
(LPSPropTagArray) &sptaSortOrder, 0, /* ansi */
&cValues, &pval);

if (hr != hrSuccess)
{
Assert(HR_FAILED(hr) || GetScode(hr) == MAPI_W_ERRORS_RETURNED);
cbSos = CbSSortOrderSet((LPSSortOrderSet) &sSortOrderContentsDefault);

/* sort order is the default */
sc = LMAlloc(&pifld->pims->lmr, cbSos, lppsSortOrder);
if (sc != S_OK)
{
hr = ResultFromScode(sc);
goto exit;
}

memcpy(*lppsSortOrder, &sSortOrderContentsDefault, (UINT) cbSos);
}
else
{
/* This property should contain a flattened array of */
/* SSortOrder structures. */

Assert(cValues == 1L);
cbSos = sizeof(ULONG) * pval->Value.MVl.cValues;

sc = LMAlloc(&pifld->pims->lmr, offsetof(SSortOrderSet, aSort) + cbSos,
lppsSortOrder);
if (sc != S_OK)
{
hr = ResultFromScode(sc);
goto exit;
}

(*lppsSortOrder)->cSorts = pval->Value.MVl.cValues / 2;
(*lppsSortOrder)->cCategories = 0;
(*lppsSortOrder)->cExpanded = 0;
memcpy((*lppsSortOrder)->aSort, pval->Value.MVl.lpl, (UINT) cbSos);
}
hr = hrSuccess;

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

LMFree(&pifld->pims->lmr, pval);
DebugTraceResult(HrGetSortOrder, hr);
return hr;
}

/*
- DestroyMessageList
-
* Purpose:
* frees allocated memory inside lppEntryList. The cValues field
* inside the entrylist must contain the count of the number of
* entryids that are in the entrylist so that the entryids can be
* freed.
*
* Parameters
* lppEntryList pointer to a list of entryids; returned NULL
*
*/
static void DestroyMessageList(PLMR plmr, LPENTRYLIST *lppEntryList)
{
Assert(lppEntryList);

if (*lppEntryList != NULL && (*lppEntryList)->lpbin != NULL)
{
ULONG cValues = (*lppEntryList)->cValues;
LPSBinary lpbin = (*lppEntryList)->lpbin;

Assert(cValues <= UINT_MAX / sizeof(SBinary));
Assert(!IsBadReadPtr(lpbin, (UINT) cValues * sizeof(SBinary)));

for (; cValues; cValues--, lpbin++)
{
Assert(lpbin->cb <= UINT_MAX);
Assert(!IsBadReadPtr(lpbin->lpb, (UINT) lpbin->cb));
LMFree(plmr, lpbin->lpb);
}

FreeNull(*lppEntryList);
}

*lppEntryList = NULL;
return;
}

/*
- HrCreateMessageList
-
* Purpose:
* Counts up and returns a list of entryids for every message in
* the given folder. If no messages are found in the folder, returns
* NULL instead of the list. To get the count, this routine uses
* FindFirstFile, FindNextFile on all files with a ".msg" extension.
* We use PR_CONTENT_COUNT to get an estimate of how many msgs are in
* the folder, but don't depend on that property being correct. If
* the property is incorrect, this routine updates it (SIDE EFFECT)
* before returning to the caller.
*
* Parameters
* pifld the folder object
* lppEntryList [out] pointer to the location to return a list of
* entryids of messages in pifld.
*
*/
static HRESULT HrCreateMessageList(PIFLD pifld, LPENTRYLIST *lppEntryList)
{
HRESULT hr;
LONG cMsgsOrig; /* value of PR_CONTENT_COUNT property */
LONG cMsgsAlloced; /* number of msgs allocated in pent->lpbin */
ULONG ichLocal; /* start of message name in full path name */
LPSBinary lpbin;
LPSBinary lpbinMac;

PEID peidNext = NULL; /* next entryID to be added to the list */
WIN32_FIND_DATA ffd;
LPTSTR szFile = NULL;
HANDLE hFindFile = FAILED_SEARCH;
LPENTRYLIST pent = NULL;

/* get the number of messages in pifld */
/* This value may be incorrect, so use it to simply get an idea */
/* of how many messages are in the folder. Allocate extra space */
/* for extra (uncounted) messages, and, if necessary, realloc */
/* the array until we really count all of them. After we've counted */
/* all messages, update PR_CONTENT_COUNT with the correct number. */
/* Assuming that there are extra messages in the folder avoids having */
/* to realloc the array as often when there actually are extra ones. */

#define CMSGS_EXTRA 10 /* # of extra spaces to alloc in msg array */

hr = HrGetSingleProp((LPMAPIPROP) pifld, &pifld->pims->lmr,
PR_CONTENT_COUNT, &cMsgsOrig);

/* Don't allow the failure to get PR_CONTENT_COUNT to keep this call */
/* from succeeding. Go ahead and do the call anyway. */

if (hr != hrSuccess)
{
hr = hrSuccess;

/* We failed to get the property. Set the "original value" */
/* to an impossible value so that we will attempt to update it */
/* at the bottom of this function. */

cMsgsOrig = -1;
cMsgsAlloced = CMSGS_EXTRA;
}
else
cMsgsAlloced = cMsgsOrig + CMSGS_EXTRA;

/* Allocate space for the entrylist. */

hr = HrAlloc(sizeof(SBinaryArray) + (cMsgsAlloced * sizeof(SBinary)),
&pent);
if (hr != hrSuccess)
goto exit;

/* We allocated the space all at once; therefore, we initialize the */
/* lpbin pointer to point just past the binary array. */

pent->lpbin = (LPSBinary) ((LPBYTE) pent + sizeof(SBinaryArray));

/* The cValues field of the entrylist must be kept up-to-date as we */
/* add entryids to the entrylist. DestroyMessageList (see above) uses */
/* that field when it frees up the entryids, and if it is wrong, the */
/* wrong number of entryids will get freed (resulting in a crash if */
/* it is too big, or a memory leak if it is too small). */

pent->cValues = 0;

lpbin = pent->lpbin;
lpbinMac = lpbin + cMsgsAlloced;

/* get the first entryID */
hr = HrFindFirstID(pifld, szMessageTemplate, &ichLocal,
&szFile, &hFindFile, &ffd, &peidNext);

if (hr != hrSuccess)
{
/* MAPI_E_NOT_FOUND means that there are no more messages in */
/* the folder. This is not an error. Because this was the first */
/* file, there were no messages in the folder at all. Therefore, */
/* free any allocated memory, reset hr, and get out. */

if (GetScode(hr) == MAPI_E_NOT_FOUND)
{
DestroyMessageList(&pifld->pims->lmr, &pent);

/* pent should be NULL now. We're going to return it to */
/* our caller below. */

AssertSz(pent == NULL, "DestroyMessageList is broken.");

hr = hrSuccess;
}

goto exit;
}

while (TRUE)
{
lpbin->cb = CbEID(peidNext);
lpbin->lpb = (LPBYTE) peidNext;

++(pent->cValues);

hr = HrFindNextID(pifld, ichLocal, szFile, hFindFile, &ffd, &peidNext);

if (hr != hrSuccess)
{
/* MAPI_E_NOT_FOUND means that there are no more messages in */
/* the folder. This is not an error. */

if (GetScode(hr) == MAPI_E_NOT_FOUND)
hr = hrSuccess;

goto exit;
}

lpbin++;

if (lpbin >= lpbinMac)
{
/* We need to realloc the array. */
cMsgsAlloced += CMSGS_EXTRA;

hr = HrRealloc((cMsgsAlloced * sizeof(SBinary))
+ sizeof(SBinaryArray), pent, &pent);
if (hr != hrSuccess)
goto exit;

/* reset the pointer to the end of the array. */
lpbinMac = pent->lpbin + cMsgsAlloced;
}
}

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

/* close the search */
CloseIDSearch(&hFindFile, &szFile);

if (hr == hrSuccess)
{
LONG cMsgsFound = 0;

/* If PR_CONTENT_COUNT was incorrect, then update it. Note that */
/* the number of messages we found is kept in pent->cValues unless */
/* we didn't find any messages (in which case, pent is NULL, and */
/* we assume no messages were found). */

if (pent)
cMsgsFound = pent->cValues;

if (cMsgsFound != cMsgsOrig)
(void) HrSetOneROFolderProp(pifld, cMsgsFound, PR_CONTENT_COUNT);

*lppEntryList = pent;
}
else if (pent)
DestroyMessageList(&pifld->pims->lmr, &pent);

DebugTraceResult(HrCreateMessageList, hr);
return hr;
}

/*
- HrDeleteSubDirectory
-
* Purpose:
* Delete a subdirectory of the current directory
*
* Parameters
* pifldParent parent folder of folder to delete
* peid entryid of folder to be deleted
* ulFlags DEL_FOLDERS and/or DEL_MESSAGES
* fContentsOnly BOOL. If TRUE, don't delete peid itself.
*
* Side effects:
* If either flag is set, it deletes all that it can before returning.
* It is possible for the subdirectory to fail at being deleted but to
* have some of its contents removed.
*
*/
static HRESULT HrDeleteSubDirectory(PIFLD pifldParent, PEID peidToDelete,
ULONG ulFlags, BOOL fContentsOnly)
{

HANDLE hFindFile = FAILED_SEARCH;
LPTSTR szToDelete = NULL; /* full path name of subdir to be deleted */
PEID peidSubDir = NULL; /* eid of subdirectory */
PIFLD pifldToDelete = NULL; /* opened subdirectory to delete */
ULONG ulOffset; /* start of local name of subdir in szSubDir */
ULONG ulObjType;
LPENTRYLIST lpMessages = NULL; /* list of messages to be deleted */
LPTSTR szRelativePath = NULL; /* relative path for id searches */
WIN32_FIND_DATA ffd;
HRESULT hr = hrSuccess;
PIMS pims = pifldParent->pims;

hr = HrFullPathName(pims->szStorePath, peidToDelete->szPath,
NULL, &szToDelete);
if (hr != hrSuccess)
goto exit;

/* open up the folder to be deleted */
hr = pifldParent->lpVtbl->OpenEntry(pifldParent, CbEID(peidToDelete),
(LPENTRYID) peidToDelete, NULL, MAPI_MODIFY,
&ulObjType, (LPUNKNOWN *) &pifldToDelete);

if (hr != hrSuccess)
goto exit;

if (!(ulFlags & DEL_FOLDERS))
{
/* The caller didn't specify DEL_FOLDERS. Check to make sure that */
/* there aren't any subfolders before allowing the deletion to */
/* continue. If there are subfolders, return MAPI_E_HAS_FOLDERS. */

/* Look for any subfolders */

hr = HrFindFirstID(pifldToDelete, szFolderTemplate, &ulOffset,
&szRelativePath, &hFindFile, &ffd, &peidSubDir);

CloseIDSearch(&hFindFile, &szRelativePath);

if (hr == hrSuccess)
{
/* release the eid */
LMFree(&pims->lmr, peidSubDir);
peidSubDir = NULL;

hr = ResultFromScode(MAPI_E_HAS_FOLDERS);
goto exit;
}
else if (GetScode(hr) != MAPI_E_NOT_FOUND)
goto exit;

hr = hrSuccess;
}
else
{
/* set up the search for subdirectories */
hr = HrFindFirstID(pifldToDelete, szFolderTemplate, &ulOffset,
&szRelativePath, &hFindFile, &ffd, &peidSubDir);

/* Delete each subdirectory*/
while (hr == hrSuccess)
{
hr = HrDeleteSubDirectory(pifldToDelete, peidSubDir, ulFlags, FALSE);

/* release the eid */
LMFree(&pims->lmr, peidSubDir);
peidSubDir = NULL;

/* Errors returned from HrDeleteSubDirectory should be fatal. */
/* Don't ignore them, because if we do, then we will leave a */
/* corrupt message store behind. MAPI_E_HAS_MESSAGES can come */
/* back from below, as well as MAPI_E_SUBMITTED, etc. */

if (hr != hrSuccess)
goto exit;

/* Delete the other subdirectories */
hr = HrFindNextID(pifldToDelete, ulOffset, szRelativePath,
hFindFile, &ffd, &peidSubDir);
}

if (GetScode(hr) == MAPI_E_NOT_FOUND)
hr = hrSuccess;
else
goto exit;

/* end the search */
CloseIDSearch(&hFindFile, &szRelativePath);
}

/* delete all messages if DEL_MESSAGES */
hr = HrCreateMessageList(pifldToDelete, &lpMessages);
if (hr != hrSuccess)
goto exit;

if (lpMessages && !(ulFlags & DEL_MESSAGES))
{
hr = ResultFromScode(MAPI_E_HAS_MESSAGES);
goto exit;
}

if (lpMessages)
{
hr = pifldToDelete->lpVtbl->DeleteMessages(pifldToDelete,
lpMessages, 0L, NULL, 0L);
if (hr != hrSuccess)
goto exit;
}

UlRelease(pifldToDelete);
pifldToDelete = NULL;

if (!fContentsOnly)
{
ULONG ulRowCount;
LPMAPITABLE pmt;

/* delete the files on the filesystem */
hr = HrDestroyFolderStorage(szToDelete);
if (hr != hrSuccess)
goto exit;

ChangeTable(pims, pifldParent->peid, peidToDelete, MAPI_FOLDER,
TABLE_ROW_DELETED, TRUE);

hr = pifldParent->lpVtbl->GetHierarchyTable(pifldParent, 0, &pmt);

if (hr == hrSuccess)
{
hr = pmt->lpVtbl->GetRowCount(pmt, 0, &ulRowCount);
UlRelease(pmt);
}

if (hr == hrSuccess && ulRowCount == 0)
hr = HrSetSubFolderProp(pifldParent, FALSE);

/* Ignore errors changing and updating the hierarchy table. */
/* The entire delete shouldn't fail simply because we couldn't */
/* update the hierarchy table. */

#ifdef DEBUG
if (hr != hrSuccess)
{
TraceSz1("HrDeleteSubDirectory: Ignoring partial failure (%s)"
" fixing hierarchy table.", SzDecodeScode(GetScode(hr)));
}
#endif

hr = hrSuccess;
}

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

UlRelease(pifldToDelete);

LMFree(&pims->lmr, peidSubDir);
FreeNull(szToDelete);

CloseIDSearch(&hFindFile, &szRelativePath);

if (lpMessages)
DestroyMessageList(&pims->lmr, &lpMessages);

DebugTraceResult(HrDeleteSubDirectory, hr);
return hr;
}

/*
- HrCopyFolder
-
* Purpose:
* moves or copies one folder into another
*
* Parameters
* peidFldSrc pointer to the entryid of the folder to copy or move.
* pifldDst the parent folder into which we copy or move
* ulFlags MAPI_MOVE, MAPI_NOREPLACE
* ptagaExcl List of properties to exclude from the copy.
*
* Returns:
*
* Errors:
*/
static HRESULT HrCopyFolder(PEID peidFldSrc, PIFLD pifldDstPar, ULONG ulFlags,
LPSPropTagArray ptagaExcl)
{
HRESULT hr;
PIFLD pifldSrc = NULL;

PIFLD pifldCopy = NULL; /* copy of pifldSrc */
LPTSTR szComment = NULL; /* comment for copy of pifldSrc */
PIMS pims = pifldDstPar->pims;
ULONG ulObjType;
LPSTR szNewFolderName;
PIFLD pifldParent = NULL;

ULONG cValNC; 
LPSPropValue pvalNC = NULL;
const static SizedSPropTagArray(2, sptaNC) =
{
2,
{
PR_DISPLAY_NAME,
PR_COMMENT
}
};

/* Open the source folder for modification only if we are moving */

hr = pims->lpVtbl->OpenEntry(pims, CbEID(peidFldSrc),
(LPENTRYID) peidFldSrc, NULL, (ulFlags & MAPI_MOVE) ? MAPI_MODIFY : 0L,
&ulObjType, (LPUNKNOWN *) &pifldSrc);
if (hr != hrSuccess)
goto exit;

/* if this is a move, make sure the source folder is not in use */
if ((ulFlags & MAPI_MOVE) && pifldSrc->cRef > 1)
{
hr = ResultFromScode(MAPI_E_NO_ACCESS);
goto exit;
}

/* get the name and comment from pifldSrc */
hr = pifldSrc->lpVtbl->GetProps(pifldSrc,
(LPSPropTagArray) &sptaNC, 0, /* ansi */
&cValNC, &pvalNC);
if (hr != hrSuccess)
{
if (GetScode(hr) == MAPI_W_ERRORS_RETURNED)
{
if (PROP_TYPE(pvalNC->ulPropTag) == PT_ERROR)
{
hr = ResultFromScode(pvalNC->Value.err);
goto exit;
}

hr = hrSuccess;
}
else
goto exit;
}

/* create new folder for the copy */
szNewFolderName = pvalNC[0].Value.LPSZ;

if (PROP_TYPE(pvalNC[1].ulPropTag) != PT_ERROR)
szComment = pvalNC[1].Value.LPSZ;
else
szComment = NULL;

hr = HrDuplicateIFLD(pifldSrc, pifldDstPar, szNewFolderName, szComment,
&pifldCopy);
if (hr != hrSuccess)
goto exit;

/* move or copy the interior of this folder (recursive) */
hr = HrCopyContents(pifldSrc, pifldCopy, ulFlags, ptagaExcl);
if (hr != hrSuccess)
goto exit;

UlRelease(pifldSrc);
pifldSrc = NULL;

/* invalidate the source folder if this is a move */
if (ulFlags & MAPI_MOVE)
{
LPMAPITABLE pmt;
ULONG ulRowCount;
LPTSTR szFullPathFolder;

hr = HrFullPathName(pims->szStorePath,
peidFldSrc->szPath, NULL, &szFullPathFolder);
if (hr != hrSuccess)
goto exit;

/* delete it */
hr = HrDestroyFolderStorage(szFullPathFolder);

FreeNull(szFullPathFolder);

if (hr != hrSuccess)
goto exit;

/* update the hierarchy table of the source's parent */
hr = HrOpenParent(pims, peidFldSrc, MAPI_MODIFY, &pifldParent);
if (hr != hrSuccess)
goto exit;

ChangeTable(pims, pifldParent->peid, peidFldSrc, MAPI_FOLDER,
TABLE_ROW_DELETED, TRUE);

hr = pifldParent->lpVtbl->GetHierarchyTable(pifldParent, 0, &pmt);

if (hr == hrSuccess)
hr = pmt->lpVtbl->GetRowCount(pmt, 0, &ulRowCount);

UlRelease(pmt);

if (hr == hrSuccess && ulRowCount == 0)
hr = HrSetSubFolderProp(pifldParent, FALSE);
}

exit:
UlRelease(pifldSrc);

LMFree(&pims->lmr, pvalNC);
UlRelease(pifldParent);

UlRelease(pifldCopy);

DebugTraceResult(HrCopyFolder, hr);
return hr;
}

/*
* HrCopyContents
*
* Purpose copy or move the contents of the source folder to
* the destination
*
* Parameters
* pifldSrc source folder
* pifldDst destination folder
* ulFlags MAPI_MOVE, MAPI_NOREPLACE, MAPI_DIALOG
* ptagaExcl List of properties to exclude from the copy.
*
*/
static HRESULT HrCopyContents(PIFLD pifldSrc, PIFLD pifldDst, ULONG ulFlags,
LPSPropTagArray ptagaExcl)
{
PEID peidNext = NULL; /* eid of next folder to move */
ULONG ichLocal;
PLMR plmr = &pifldSrc->pims->lmr;

/* relative path name of next folder to be moved */
LPTSTR szFile = NULL;
HANDLE hFindFile = FAILED_SEARCH;
HRESULT hr = hrSuccess;
ULONG ulMessageFlags = 0L; /* flags for the message copy */
WIN32_FIND_DATA ffd;

/* move or copy each message inside the folder */
if (ulFlags & MAPI_MOVE)
ulMessageFlags = MESSAGE_MOVE;

if (!FContainsProp(PR_CONTAINER_CONTENTS, ptagaExcl))
{
LPENTRYLIST lpMessages = NULL;

/* make a message list */
hr = HrCreateMessageList(pifldSrc, &lpMessages);
if (hr != hrSuccess)
goto exit;

/* move/copy messages */
if (lpMessages)
{
hr = pifldSrc->lpVtbl->CopyMessages(pifldSrc,
lpMessages, 0, (LPMAPIFOLDER) pifldDst, 0,
NULL, ulMessageFlags);

DestroyMessageList(plmr, &lpMessages);

if (hr != hrSuccess)
goto exit;
}
}

/* move/copy subfolders if required */
if ((ulFlags & MAPI_MOVE)
|| (!FContainsProp(PR_CONTAINER_HIERARCHY, ptagaExcl)))
{
/* get the first entryID for a subfolder */
hr = HrFindFirstID(pifldSrc, szFolderTemplate, &ichLocal,
&szFile, &hFindFile, &ffd, &peidNext);

while (hr == hrSuccess)
{
/* copy the eid to the destination */
hr = HrCopyFolder(peidNext, pifldDst, ulFlags, ptagaExcl);
if (hr != hrSuccess)
goto exit;

LMFree(plmr, peidNext);
peidNext = NULL;

if (ulFlags & MAPI_MOVE)
{
/* The folder should have been destroyed; therefore, */
/* start the search again. */

CloseIDSearch(&hFindFile, &szFile);

hr = HrFindFirstID(pifldSrc, szFolderTemplate, &ichLocal,
&szFile, &hFindFile, &ffd, &peidNext);
}
else
hr = HrFindNextID(pifldSrc, ichLocal, szFile,
hFindFile, &ffd, &peidNext);
}

if (GetScode(hr) != MAPI_E_NOT_FOUND)
goto exit; /* uh oh. got a real error */

hr = hrSuccess;
}

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

/* close the search */
CloseIDSearch(&hFindFile, &szFile);
LMFree(plmr, peidNext);

DebugTraceResult(HrCopyContents, hr);
return hr;
}

/*
* HrEIDFromDisplayName
*
* Purpose
* Searches the table given, looking for a matching PR_DISPLAY_NAME.
* If found, the routine returns the value of the corresponding
* PR_ENTRYID column, converted to an internal PEID. If not found, the
* routine returns a NULL.
*
* Parameters
* pmt: The IMAPITable object to search for the display name.
* szName: The display name to search for.
* plmr: A pointer to the linked memory routines to allow freeing memory.
* ppeid: A pointer to the location to return the internal entryid of
* the PR_ENTRYID column that corresponds to the matching display
* name. If no matching display name was found, the routine returns
* NULL in this location.
*
*/
static HRESULT HrEIDFromDisplayName(LPMAPITABLE pmt, LPSTR szName, PLMR plmr,
PEID *ppeid)
{
HRESULT hr;
LPSRowSet prws = NULL;
PEID peid = NULL;

SizedSPropTagArray(2, spta) =
{
2,
{
PR_ENTRYID,
PR_DISPLAY_NAME
}
};

hr = pmt->lpVtbl->SetColumns(pmt, (LPSPropTagArray) &spta, 0);
if (hr != hrSuccess)
goto exit;

hr = pmt->lpVtbl->SeekRow(pmt, BOOKMARK_BEGINNING, 0, NULL);
if (hr != hrSuccess)
goto exit;

while (TRUE)
{
LPSTR szCurName;
LPSPropValue pval;

hr = pmt->lpVtbl->QueryRows(pmt, 1, 0, &prws);
if (hr != hrSuccess)
goto exit;

if (prws->cRows == 0)
break;

Assert(prws);
Assert(prws->cRows == 1);
Assert(prws->aRow[0].lpProps);
Assert(prws->aRow[0].lpProps[0].ulPropTag == PR_ENTRYID);
Assert(prws->aRow[0].lpProps[1].ulPropTag == PR_DISPLAY_NAME);

pval = prws->aRow[0].lpProps;

szCurName = (LPSTR) pval[1].Value.lpszA;

if (lstrcmpi(szName, szCurName) == 0)
{
SCODE sc;
PEID peidTemp = (PEID) pval->Value.bin.lpb;
UINT cbEID = (UINT) pval->Value.bin.cb;

sc = LMAlloc(plmr, cbEID, &peid);
if (sc != S_OK)
{
hr = ResultFromScode(sc);
goto exit;
}

if (cbEID)
memcpy(peid, peidTemp, cbEID);

break;
}

FreeProws(prws);
prws = NULL;
}

exit:
if (prws)
{
if (prws->cRows && prws->aRow[0].lpProps)
LMFree(plmr, prws->aRow[0].lpProps);

LMFree(plmr, prws);
}

if (hr == hrSuccess)
{
AssertSz(!IsBadWritePtr(ppeid, sizeof(PEID)), "Bad parameter (ppeid) "
"passed to HrEIDFromDisplayName");
*ppeid = peid;
}

DebugTraceResult(HrEIDFromDisplayName, hr);
return hr;
}