TFLDRARY.C

// --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);
}

// -----------------------------------------------------------------------------