// --TFldrAry.C----------------------------------------------------------------
//
// This module contains functions for maintaining and using a topic folder
// array for SMBAGENT.
//
// The array is essentialy a dynamicly expanding row set of properties of all
// topic folders. Each row contains a Display Name and an Entry Id. The array
// is sorted by display name.
//
// See TOPCACHE.H for details on the relationship between the STFolderArray, the
// STopicCache, and the STopic objects.
//
// Copyright (C) Microsoft Corp. 1986-1996. All Rights Reserved.
// -----------------------------------------------------------------------------
#include "edk.h"
// Cause protected functions to be available.
#define FRIEND_OF_STOPIC
#define FRIEND_OF_STFOLDERARRAY
#define FRIEND_OF_STOPICCACHE
#include "smbagent.h"
#include "TFldrAry.CHK"
//$--Compare_DispName-----------------------------------------------------------
// Used by search routines to compare a search key to the display name which is
// the first property of a row in a SRowSet.
//
// This helper function is globaly available.
// RETURNS: -1 if Search Key < Display Name
// 0 if Search Key = Display Name
// 1 if Search Key > Display Name
// -----------------------------------------------------------------------------
PROTECTED int Compare_DispName( const void* lpszSrchKey, const void* lpRow)
{
return( lstrcmpi( (LPTSTR) lpszSrchKey, ((LPSRow) lpRow)->lpProps->Value.LPSZ));
}
// -----------------------------------------------------------------------------
// This object contains an array of all folders. The functions whose
// names that begin with "STFolderArray_" work on this object.
// -----------------------------------------------------------------------------
STFolderArray TFolderArray;
//$--STFolderArray_FindInsertionPt----------------------------------------------
// RECURSIVE binary search routine to find the insertion point for a folder.
// If the topic folder name already exists one or more times in the array the
// insertion point will be after the last item of the same name.
// RETURNS: the index to insert topic folder at.
// -----------------------------------------------------------------------------
static ULONG STFolderArray_FindInsertionPt(
IN LPTSTR lpszTFolderName, // The name of the topic folder you want to insert.
IN ULONG cRows, // Can not be zero.
IN LPSRow lpRow)
{
int nCmp = 0;
ULONG iMid = cRows / 2;
nCmp = Compare_DispName( lpszTFolderName, lpRow + iMid);
if( nCmp < 0)
{ // Topic folder name is less than topic folder row.
if( iMid == 0)
return( 0);
return( STFolderArray_FindInsertionPt( lpszTFolderName, iMid, lpRow));
}
else
{ // Topic folder name is greater than or equal to topic folder row.
iMid ++;
if( iMid == cRows)
return( iMid);
return( STFolderArray_FindInsertionPt( lpszTFolderName, cRows - iMid, lpRow + iMid) + iMid);
}
}
//$--STFolderArray_HrInsert------------------------------------------------------
// Insert a new property value array in the row set of the topic folder array.
// This also adjusts the topic cache which has references to this array by index.
// -----------------------------------------------------------------------------
static HRESULT STFolderArray_HrInsert(
IN ULONG iTFolderArray, // Index to insert a SRow object at.
IN LPTSTR lpszTFolderName, // The name of the topic folder you want to insert.
IN ULONG cbEID, // The count of bytes of the entry id.
IN LPENTRYID lpEID) // The entry id of the topic folder you want to insert.
{
HRESULT hr = NOERROR;
ULONG cBytes = 0;
LPBYTE lpBuf = NULL;
LPSRow lpRow = NULL;
ULONG cExpand = 10; // Number of items to expand array when necessary.
ULONG cTFNameSize = 0;
DEBUGPUBLIC( "STFolderArray_HrInsert()");
hr = CHK_STFolderArray_HrInsert( iTFolderArray, lpszTFolderName, cbEID, lpEID);
if( FAILED( hr))
RETURN( hr);
// Is the array big enough to insert a SRow?
if( TFolderArray.lpRows->cRows == TFolderArray.cAllocatedRows)
{ // NO, so expand it.
// Allocate a new buffer for the row set.
cBytes = CbNewSRowSet( TFolderArray.cAllocatedRows + cExpand);
hr = MAPIAllocateBuffer( cBytes, &lpBuf);
if( FAILED( hr) || !lpBuf)
{
hr = HR_LOG( E_OUTOFMEMORY);
goto cleanup;
}
// Zero the new buffer.
memset( lpBuf, 0, cBytes);
// Was there any rows in the last allocation?
if( TFolderArray.cAllocatedRows)
{ // YES, so move the old buffer to the new one.
cBytes = CbNewSRowSet( TFolderArray.cAllocatedRows);
memmove( lpBuf, TFolderArray.lpRows, cBytes);
}
// Free the old buffer and use the new pointer. Even if the last
// alocation contained no rows there was minimum allocation for the
// SRowSet structure with a count of zero. We do NOT use FREEPROWS
// since it would free each row which we just moved to our new buffer.
MAPIFreeBuffer( TFolderArray.lpRows);
TFolderArray.lpRows = (LPSRowSet) lpBuf;
// Increase the maximum size of the array.
TFolderArray.cAllocatedRows += cExpand;
}
// Allocate memory for the property value array.
cTFNameSize = (lstrlen( lpszTFolderName) + 1) * sizeof(TCHAR);
cBytes = sizeof( SPropValue) * 2 + cTFNameSize + cbEID;
hr = MAPIAllocateBuffer( cBytes, &lpBuf);
if( FAILED( hr) || !lpBuf)
{
hr = HR_LOG( E_OUTOFMEMORY);
goto cleanup;
}
// The address of the row we wish to place data at.
lpRow = TFolderArray.lpRows->aRow + iTFolderArray;
// Are we inserting in the middle of the array?
if( iTFolderArray < TFolderArray.lpRows->cRows)
{ // YES, so make space for the new entry.
ULONG cBytes = (TFolderArray.lpRows->cRows - iTFolderArray) * sizeof( SRow);
memmove( lpRow + 1, lpRow, cBytes);
memset( lpRow, 0, sizeof( SRow));
// Adjust the index in the topic cache that references any index >= iTFolderArray.
STopicCache_AdjustIndex( iTFolderArray, 1);
}
// Increment the count of rows.
TFolderArray.lpRows->cRows ++;
// Initialize the SRow structure object.
lpRow->cValues = 2;
lpRow->lpProps = (LPSPropValue) lpBuf;
lpBuf += sizeof( SPropValue) * 2;
// Setup the PR_DISPLAY_NAME property.
lpRow->lpProps[0].ulPropTag = PR_DISPLAY_NAME;
lpRow->lpProps[0].Value.LPSZ = lpBuf;
memmove( lpBuf, lpszTFolderName, cTFNameSize); // Copies trailing null as well.
lpBuf += cTFNameSize;
// Setup the PR_ENTRYID property.
lpRow->lpProps[1].ulPropTag = PR_ENTRYID;
lpRow->lpProps[1].Value.bin.cb = cbEID;
lpRow->lpProps[1].Value.bin.lpb = lpBuf;
memmove( lpBuf, lpEID, cbEID);
cleanup:
RETURN( hr);
}
//$--STFolderArray_HrInit---------------------------------------------------------
// This MUST be called only once at the begining before using the topic folders
// or the topic cache functions. Be sure to use STFolderArray_Destroy() when
// done with the array.
//
// Initialize the global topic folder array by filling it with the display name
// and entry id of all folders under the topics folder.
// -----------------------------------------------------------------------------
HRESULT STFolderArray_HrInit()
{
HRESULT hr = NOERROR;
LPMAPITABLE lpTopicsTable = NULL;
SizedSPropTagArray( 2, PropTagArray) = { 2, { PR_DISPLAY_NAME, PR_ENTRYID}};
SizedSSortOrderSet( 1, SortOrderSet) = { 1, 0, 0, { PR_DISPLAY_NAME, TABLE_SORT_ASCEND}};
DEBUGPUBLIC( "STFolderArray_HrInit()");
// Initialize to an empty array.
TFolderArray.cAllocatedRows = 0;
TFolderArray.lpRows = NULL;
// Make sure the global lpTopicsFolder is initialized.
if( !lpTopicsFolder)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}
// Open an interface to a table that will give us a list of all topic folders.
hr = MAPICALL( lpTopicsFolder)->GetHierarchyTable( lpTopicsFolder,
MAPI_DEFERRED_ERRORS, &lpTopicsTable);
if( FAILED( hr))
goto cleanup;
// Get all folder rows in a sorted order. If there were no rows we
// still get a pointer to a SRowSet structure with a count of zero.
hr = HrQueryAllRows( lpTopicsTable, (LPSPropTagArray) &PropTagArray, NULL,
(LPSSortOrderSet) &SortOrderSet, 0, &TFolderArray.lpRows);
if(FAILED( hr))
goto cleanup;
// Set the number of rows the current allocation of the array will hold.
TFolderArray.cAllocatedRows = TFolderArray.lpRows->cRows;
cleanup:
ULRELEASE( lpTopicsTable);
RETURN( hr);
}
//$--STFolderArray_Find---------------------------------------------------------
// Find a topic folder in the global topic folder array.
// RETURNS: The index of the topic or NOT_FOUND.
// -----------------------------------------------------------------------------
ULONG STFolderArray_Find(
IN LPTSTR lpszTFolderName) // The name of the topic folder you want to find.
{
LPBYTE lpTFolder = bsearch(
lpszTFolderName,
TFolderArray.lpRows->aRow,
STFolderArray_GetCount(),
sizeof( SRow),
Compare_DispName);
if( lpTFolder)
return( (lpTFolder - ((LPBYTE) TFolderArray.lpRows->aRow)) / sizeof( SRow));
return( NOT_FOUND);
}
//$--STFolderArray_HrCreateFolder------------------------------------------------
// Create a new topic folder and insert into the array so that the array remains
// sorted by PR_DISPLAY_NAME and we keep the entry id. This will also place
// this folder in the topic cache with just the folder open.
//
// OUTPUT: lppNewFolder Open folder interface ptr. Do NOT release this.
// lppTopic Open Topic cache object pointer.
// -----------------------------------------------------------------------------
HRESULT STFolderArray_HrCreateFolder(
IN LPTSTR lpszTFolderName, // The name of the topic folder you want to create.
OUT LPMAPIFOLDER* lppNewFolder, // Folder interface ptr to create.
OUT STopic** lppTopic) // Open Topic cache object pointer.
{
HRESULT hr = NOERROR;
ULONG iTFolderArray = NOT_FOUND;
ULONG cValues = 0L;
LPSPropValue lpProp = NULL; // Returned binary data
static const SizedSPropTagArray(1,PropEID) = {1, {PR_ENTRYID}};
DEBUGPUBLIC( "STFolderArray_HrCreateFolder()");
hr = CHK_STFolderArray_HrCreateFolder( lpszTFolderName, lppNewFolder, lppTopic);
if( FAILED( hr))
RETURN( hr);
// Initialize output ptrs to NULL in case of failure.
*lppNewFolder = NULL;
*lppTopic = NULL;
// Create the MAPI folder.
hr = MAPICALL( lpTopicsFolder)->CreateFolder( lpTopicsFolder,
FOLDER_GENERIC,
lpszTFolderName,
lpszTFolderName,
NULL,
fMapiUnicode | OPEN_IF_EXISTS | MAPI_DEFERRED_ERRORS,
lppNewFolder);
if( FAILED( hr))
goto cleanup;
ASSERT_IUNKNOWN_PTR( *lppNewFolder, "INVALID new folder pointer");
hr = MAPICALL( *lppNewFolder)->GetProps( *lppNewFolder,
(LPSPropTagArray)&PropEID,
fMapiUnicode,
&cValues,
&lpProp);
if( FAILED(hr) || hr == MAPI_W_ERRORS_RETURNED)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}
ASSERTERROR( cValues != 0, "NO property values returned!");
ASSERTERROR( lpProp->ulPropTag == PR_ENTRYID, "INVALID property tag returned!");
// Find a slot in this array to insert this topic folder at.
if( TFolderArray.lpRows->cRows == 0)
iTFolderArray = 0;
else
iTFolderArray = STFolderArray_FindInsertionPt( lpszTFolderName,
TFolderArray.lpRows->cRows, TFolderArray.lpRows->aRow);
// Insert new topic folder info into the array and adjust the topic cache.
hr = STFolderArray_HrInsert( iTFolderArray, lpszTFolderName,
(ULONG)lpProp->Value.bin.cb, (LPENTRYID)lpProp->Value.bin.lpb);
if( FAILED(hr) && hr != MAPI_E_NETWORK_ERROR)
{
if( FAILED( HrDeleteTopicFolder(
(ULONG)lpProp->Value.bin.cb, (LPENTRYID)lpProp->Value.bin.lpb)));
goto cleanup;
}
// Place just the folder interface ptr in the first topic slot of the cache.
STopicCache_SetTopicFolder( iTFolderArray, *lppNewFolder, lppTopic);
cleanup:
MAPIFREEBUFFER( lpProp);
if( FAILED( hr))
ULRELEASE( *lppNewFolder);
RETURN( hr);
}
//$--STFolderArray_HrDeleteFolder-----------------------------------------------
// Deletes a topic folder from the MAPI store and the TFolderArray. This also
// removes it from the topic cache and adjusts the indexed references to this array.
//
// NOTE: Use STFolderArray_HrDeleteFolderSZ() if you only have the topic folder
// name and not the index.
// -----------------------------------------------------------------------------
HRESULT STFolderArray_HrDeleteFolder(
IN ULONG iTFolderArray) // Index of folder to be deleted.
{
HRESULT hr = NOERROR;
ULONG cBytes = 0;
ULONG cbEID = 0; // Count of bytes in Entry ID
LPENTRYID lpEID = NULL; // Pointer to Entry ID.
LPSRow lpRow = NULL;
DEBUGPUBLIC( "STFolderArray_HrDeleteFolder()");
hr = CHK_STFolderArray_HrDeleteFolder( iTFolderArray);
if( FAILED( hr))
RETURN( hr);
// Delete the folder from the MAPI store and any messages that are in it.
// This must be done before freeing the buffer that contains the entry id.
cbEID = STFolderArray_GetCbEID( iTFolderArray);
lpEID = STFolderArray_GetEID( iTFolderArray);
hr = HrDeleteTopicFolder( cbEID, lpEID);
if( FAILED( hr))
goto cleanup;
// Remove the item from the cache and adjust the index in the
// topic cache that references any index >= iTFolderArray.
STopicCache_DeleteTopic( iTFolderArray);
// The address of the row in the folder array we wish delete.
lpRow = TFolderArray.lpRows->aRow + iTFolderArray;
// Free the buffer that contains the properties for this row.
MAPIFREEBUFFER( lpRow->lpProps);
// We now have one less item in the array.
TFolderArray.lpRows->cRows --;
// Are we deleting from the middle of the array?
if( iTFolderArray < TFolderArray.lpRows->cRows)
{ // YES, so move all items up by one entry.
cBytes = (TFolderArray.lpRows->cRows - iTFolderArray) * sizeof( SRow);
memmove( lpRow, lpRow + 1, cBytes);
}
// Zero the memory of the old last item in the array.
memset( TFolderArray.lpRows->aRow + TFolderArray.lpRows->cRows, 0, sizeof( SRow));
cleanup:
RETURN( hr);
}
// -----------------------------------------------------------------------------