// --Topics.c-------------------------------------------------------------------
//
// This module contains functions for maintaining and using a topic object for
// SMBAGENT. A topic has a folder, an introductory message, and a list of
// subscribers.
//
// The list of subscribers are contained in the introductory
// message as the "TO" recipient list. The introductory message is contained in
// the topic folder. The topic folder is contained in a root topics folder.
//
// 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
#include "smbagent.h"
#include "topics.CHK"
// -----------------------------------------------------------------------------
// Used to open the intro message.
static SizedSPropTagArray( 1, ptaIntroMsg) = { 1, {PR_ENTRYID}};
static LPSPropTagArray lpptaIntroMsg = (LPSPropTagArray) &ptaIntroMsg;
// Used to load all subscribers.
static SizedSPropTagArray( 6, ptaSubscriber) =
{ 6,
{
PR_DISPLAY_NAME, // PT_STRING
PR_ENTRYID, // PT_BINARY // for verifying an existing entry
PR_ADDRTYPE, // PT_STRING
PR_EMAIL_ADDRESS, // PT_STRING
PR_RECIPIENT_TYPE, // PT_LONG
PR_ROWID, // PT_LONG
}
};
static LPSPropTagArray lpptaSubscriber = (LPSPropTagArray) &ptaSubscriber;
//$--STopic_HrOpenIntroMsg------------------------------------------------------
// Open the topic message in the already opened topic folder.
//
// The intro message has been stored in the MAPI_ASSOCIATED portion of the topic
// folder. At the time we saved it we gave it a unique message class so that
// we could use the FindRow() function to locate it. This is a quick opperation
// since very few items will ever be stored in this location.
//
// We chose this method rather than storing it in the main portion of the topic
// folder to avoid sorting the folder, which could get quite large. Sorting a
// folder is an expensive opperation at the server end and should only be used
// when it is necessary.
// -----------------------------------------------------------------------------
static HRESULT STopic_HrOpenIntroMsg(
THIS STopic* lpTopic) // The topic object we are working on.
{
HRESULT hr = NOERROR;
ULONG ulObjType = 0;
ULONG cbEID = 0;
LPENTRYID lpEID = NULL;
LPMAPITABLE lpMsgTable = NULL;
LPSRowSet lpRow = NULL;
static SPropValue RestrictProp = { PR_MESSAGE_CLASS, 0L, { 0}};
static SRestriction Restrict = { RES_PROPERTY, { RELOP_EQ, PR_MESSAGE_CLASS, (ULONG)&RestrictProp}};
DEBUGPRIVATE ("STopic_HrOpenIntroMsg()");
hr = CHK_STopic_THIS( lpTopic);
if( FAILED( hr))
RETURN( hr);
RestrictProp.Value.LPSZ = INTRO_MSG_CLASS;
// Get the contents table of the topic folder.
hr = MAPICALL( lpTopic->lpFolder)->GetContentsTable( lpTopic->lpFolder,
MAPI_ASSOCIATED | MAPI_DEFERRED_ERRORS,
&lpMsgTable);
if( FAILED( hr))
goto cleanup;
// Set the columns to return the entry id.
hr = MAPICALL( lpMsgTable)->SetColumns( lpMsgTable,
lpptaIntroMsg, TBL_BATCH);
if( FAILED( hr))
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}
// Find just intro message.
hr = MAPICALL( lpMsgTable)->Restrict( lpMsgTable,
(LPSRestriction)&Restrict, TBL_BATCH);
if( FAILED( hr))
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}
// Get the intro message's EID.
hr = MAPICALL( lpMsgTable)->QueryRows( lpMsgTable, 1, 0, &lpRow);
if( FAILED( hr) || !lpRow || !lpRow->cRows)
{ // If we don't get at least one message there is a problem with this topic.
if( hr != MAPI_E_NETWORK_ERROR)
{
MODULE_ERROR1( "Intro message for topic '%s' not found!", STFolderArray_GetName(lpTopic->iTFolderArray));
hr = HR_LOG( EDK_E_NOT_FOUND);
}
goto cleanup;
}
// Open the intro message for modification.
cbEID = lpRow->aRow[0].lpProps->Value.bin.cb;
lpEID = (LPENTRYID) (lpRow->aRow[0].lpProps->Value.bin.lpb);
hr = MAPICALL( lpTopic->lpFolder)->OpenEntry( lpTopic->lpFolder,
cbEID,
lpEID,
NULL,
MAPI_MODIFY | MAPI_DEFERRED_ERRORS,
&ulObjType,
(LPUNKNOWN*)&lpTopic->lpIntroMsg);
if( FAILED(hr) || ulObjType != MAPI_MESSAGE || !lpTopic->lpIntroMsg)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}
cleanup:
ULRELEASE(lpMsgTable);
FREEPROWS(lpRow);
RETURN( hr);
}
//$--STopic_HrReadSubscriberList-----------------------------------------------
// Reads the entire list of subscribers into a SRowSet structure.
// ----------------------------------------------------------------------------
static HRESULT STopic_HrReadSubscriberList(
THIS STopic* lpTopic) // The topic object we are working on.
{
HRESULT hr = NOERROR;
static BOOL fInRetryMode = FALSE; // To recover from MAPI_E_CALL_FAILED.
DEBUGPRIVATE ("STopic_HrReadSubscriberList()");
hr = CHK_STopic_THIS( lpTopic);
if( FAILED( hr))
RETURN( hr);
// Open the table of recipients.
if( !lpTopic->lpSubTable)
{
hr = MAPICALL( lpTopic->lpIntroMsg)->GetRecipientTable( lpTopic->lpIntroMsg,
MAPI_DEFERRED_ERRORS,
&lpTopic->lpSubTable);
if( FAILED(hr))
goto cleanup;
}
// Read the entire list of subscribers into a row set structure.
hr = HrQueryAllRows( lpTopic->lpSubTable, lpptaSubscriber, NULL, NULL, 0,
&lpTopic->lpSubscriberRowSet);
if( hr == MAPI_E_CALL_FAILED && !fInRetryMode)
{ // Sometimes this call fails. We can recover from this if we
// reopen the intro message and try again. WARNING: this is a
// recursive call.
ULRELEASE( lpTopic->lpSubTable);
ULRELEASE( lpTopic->lpIntroMsg);
hr = STopic_HrOpenIntroMsg( lpTopic);
if( FAILED( hr))
goto cleanup;
fInRetryMode = TRUE;
hr = STopic_HrReadSubscriberList( lpTopic);
fInRetryMode = FALSE;
if( FAILED( hr))
goto cleanup;
// If previous call was successful, all currency pointers
// are already set.
goto cleanup;
}
if( FAILED( hr) || !lpTopic->lpSubscriberRowSet ||
(lpTopic->lpSubscriberRowSet && !lpTopic->lpSubscriberRowSet->cRows))
{ // If we don't find at least one subscriber (topic creator) there is
// a problem with the intro message.
if( hr != MAPI_E_NETWORK_ERROR)
hr = HR_LOG( EDK_E_NOT_FOUND);
goto cleanup;
}
// Set subscriber currency pointers.
lpTopic->lpCurSubscriber = NULL;
lpTopic->lpLastSubscriber = lpTopic->lpSubscriberRowSet->aRow +
(lpTopic->lpSubscriberRowSet->cRows - 1);
cleanup:
//ULRELEASE( lpTopic->lpSubTable);
RETURN( hr);
}
//$--STopic_Init----------------------------------------------------------------
// Initialize a topic cache item, setting it to an empty state.
// -----------------------------------------------------------------------------
PROTECTED void STopic_Init(
THIS STopic* lpTopic) // The topic object we are working on.
{
// Zero everything except the index which is set to NOT_FOUND.
memset( lpTopic, 0, sizeof( STopic));
lpTopic->iTFolderArray = NOT_FOUND;
}
//$--STopic_Release-------------------------------------------------------------
// Release and free any open interfaces or buffers in the topic cache object and
// re-initialize to an empty state.
// -----------------------------------------------------------------------------
PROTECTED void STopic_Release(
THIS STopic* lpTopic) // The topic object we are working on.
{
lpTopic->iTFolderArray = NOT_FOUND;
lpTopic->cUsageCnt = 0;
ULRELEASE( lpTopic->lpFolder);
ULRELEASE( lpTopic->lpIntroMsg);
FREEPROWS( lpTopic->lpSubscriberRowSet);
lpTopic->lpCurSubscriber = NULL;
lpTopic->lpLastSubscriber = NULL;
ULRELEASE( lpTopic->lpSubTable);
}
//$--STopic_HrOpen--------------------------------------------------------------
// Open all interface pointers and load the subscriber list for this topic cache
// item. Some things may already be opened or loaded while others may not.
// -----------------------------------------------------------------------------
PROTECTED HRESULT STopic_HrOpen(
THIS STopic* lpTopic, // The topic object we are working on.
IN ULONG iTFolderArray) // Index into folder array used to initialize this topic.
{
HRESULT hr = NOERROR;
ULONG ulObjType = 0;
ULONG cbEID = 0;
LPENTRYID lpEID = NULL;
LPMAPITABLE lpMsgTable = NULL;
LPSRowSet lpRow = NULL;
DEBUGPUBLIC ("STopic_HrInit()");
hr = CHK_STopic_HrInit( lpTopic, iTFolderArray);
if( FAILED( hr))
RETURN( hr);
// If the topic already has a TFolderArray index verify
// that it matches what we are trying to load now.
if( lpTopic->iTFolderArray == NOT_FOUND)
lpTopic->iTFolderArray = iTFolderArray; // Remember the index of this topic folder.
else if( lpTopic->iTFolderArray != iTFolderArray)
{ // This has not been released.
hr = HR_LOG( E_FAIL);
goto cleanup;
}
// Make sure the folder is not already opened.
if( !lpTopic->lpFolder)
{ // NO, it's not open so open the folder iterface for this topic.
cbEID = STFolderArray_GetCbEID( iTFolderArray);
lpEID = STFolderArray_GetEID( iTFolderArray);
hr = MAPICALL( lpTopicsFolder)->OpenEntry( lpTopicsFolder, cbEID, lpEID, NULL,
MAPI_MODIFY|MAPI_DEFERRED_ERRORS, &ulObjType, (LPUNKNOWN FAR *) &lpTopic->lpFolder);
if( FAILED( hr))
goto cleanup;
}
// Make sure the introductory message is not already opened.
if( !lpTopic->lpIntroMsg)
{ // NO, it's not open so open the introductory message.
hr = STopic_HrOpenIntroMsg( lpTopic);
if( FAILED( hr))
goto cleanup;
}
// Make sure the subscriber list is not already loaded.
if( !lpTopic->lpSubscriberRowSet)
{ // NO, it's not loaded so read the subscriber list into memory.
hr = STopic_HrReadSubscriberList( lpTopic);
if( FAILED( hr))
goto cleanup;
}
// Reset the subscriber currency.
lpTopic->lpCurSubscriber = NULL;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cleanup:
if( FAILED( hr))
STopic_Release( lpTopic);
RETURN( hr);
}
//$--STopic_HrFindSubscriber----------------------------------------------------
// Find the sender as a subscriber in the topic object and places its index or
// NOT_FOUND in lpiSubscriber.
//
// RETURNS: HRESULT error only on failure. NOT_FOUND is NOT an error!
// -----------------------------------------------------------------------------
HRESULT STopic_HrFindSubscriber(
THIS STopic* lpTopic, // The topic object we are working on.
OUT ULONG* lpiSubscriber) // Index of subscriber.
{
HRESULT hr = NOERROR;
LPSBinary lpBinRow = NULL;
LPSBinary lpBinSrchKey = GetSenderEntryIdPtr();
LPSRow lpRow = lpTopic->lpSubscriberRowSet->aRow;
ULONG iSubscriber = 0L;
ULONG fFoundAMatch = FALSE;
DEBUGPUBLIC ("STopic_HrFindSubscriber()");
*lpiSubscriber = NOT_FOUND;
// Search for the entry id in our row set.
for( iSubscriber = 0;
iSubscriber < STopic_GetSubscriberCount( lpTopic);
iSubscriber++, lpRow++)
{
lpBinRow = &(lpRow->lpProps[IDX_ENTRYID].Value.bin);
hr = MAPICALL( lpAdrBook)->CompareEntryIDs( lpAdrBook,
lpBinSrchKey->cb, (LPENTRYID) lpBinSrchKey->lpb,
lpBinRow->cb, (LPENTRYID) lpBinRow->lpb, 0, &fFoundAMatch);
if( FAILED( hr))
goto cleanup;
if( fFoundAMatch)
{
*lpiSubscriber = iSubscriber;
goto cleanup;
}
}
cleanup:
RETURN( hr);
}
//$--STopic_GetFirstSubscriber--------------------------------------------------
// RETURN the first subscriber's row of properties.
// -----------------------------------------------------------------------------
LPSRow STopic_GetFirstSubscriber(
THIS STopic* lpTopic) // The topic object we are working on.
{
DEBUGPUBLIC ("STopic_GetFirstSubscriber()");
if( !lpTopic->lpSubscriberRowSet)
return( NULL);
lpTopic->lpCurSubscriber = lpTopic->lpSubscriberRowSet->aRow;
return( lpTopic->lpCurSubscriber);
}
//$--STopic_GetNextSubscriber---------------------------------------------------
// RETURN the next subscriber's row of properties.
// -----------------------------------------------------------------------------
LPSRow STopic_GetNextSubscriber(
THIS STopic* lpTopic) // The topic object we are working on.
{
DEBUGPUBLIC ("STopic_GetNextSubscriber()");
// Had we already reached past the end of the list?
if( lpTopic->lpCurSubscriber)
{ // NO. Then are we currently at the end of the list?
if( lpTopic->lpCurSubscriber == lpTopic->lpLastSubscriber)
lpTopic->lpCurSubscriber = NULL; // YES, we have reached the end.
else
lpTopic->lpCurSubscriber ++; // Nope, there are more subscribers.
}
return( lpTopic->lpCurSubscriber);
}
// -----------------------------------------------------------------------------
// Modifies the list of subscribers in the intro message and in memory. You
// should use STopic_HrAddSubscriber() or STopic_HrRemoveSubscriber() and not
// this function directly.
//
// NOTE: The subscriber list will be invalid after this call.
// -----------------------------------------------------------------------------
HRESULT STopic_HrModifySubscriberList(
THIS STopic* lpTopic, // The topic object we are working on.
IN LPADRLIST lpAdrList, // The address list to add or remove.
IN ULONG ulFlags) // MODRECIP_ADD or MODRECIP_REMOVE
{
HRESULT hr = NOERROR;
DEBUGPUBLIC ("STopic_HrModifySubscriberList()");
// Add or Remove the entry to or from the subscriber list in the intro message.
hr = MAPICALL( lpTopic->lpIntroMsg)->ModifyRecipients( lpTopic->lpIntroMsg,
ulFlags, lpAdrList);
if( FAILED( hr))
goto cleanup;
// Save changes to the message.
hr = MAPICALL( lpTopic->lpIntroMsg)->SaveChanges( lpTopic->lpIntroMsg,
FORCE_SAVE | KEEP_OPEN_READWRITE);
if( FAILED(hr))
goto cleanup;
// Free the subscriber list so that next time we work with this
// topic we will get a fresh list with updated row ids.
STopic_FreeSubscriberList( lpTopic);
cleanup:
RETURN( hr);
}
//$--STopic_HrDeleteSubscriber--------------------------------------------------
// Delete the subscriber from the recipient list of the Introductory message and
// then free the subscriber list so that next time we work with this topic we
// will get a fresh list with updated row ids.
//
// NOTE: The subscriber list will be invalid after this call.
// -----------------------------------------------------------------------------
HRESULT STopic_HrDeleteSubscriber(
THIS STopic* lpTopic, // The topic object we are working on.
IN ULONG iSubscriber) // The index of the subscriber to delete.
{
HRESULT hr = NO_ERROR;
ULONG cBytes = 0;
LPADRLIST lpAdrList = NULL;
LPSRow lpSubscriberRow = NULL;
DEBUGPUBLIC( "STopic_HrDeleteSubscriber()");
hr = CHK_STopic_HrDeleteSubscriber( lpTopic, iSubscriber);
if( FAILED( hr))
RETURN( hr);
// Allocate memory for the address list buffer.
cBytes = CbNewADRLIST(1);
hr = MAPIAllocateBuffer( cBytes, &lpAdrList);
if( FAILED( hr))
{
hr = HR_LOG( E_OUTOFMEMORY);
goto cleanup;
}
memset( lpAdrList, 0, cBytes);
// Build an address list with one entry using the properties
// found in the subscriber row passed in by the user.
lpSubscriberRow = lpTopic->lpSubscriberRowSet->aRow + iSubscriber;
lpAdrList->cEntries = 1;
lpAdrList->aEntries[0].cValues = lpSubscriberRow->cValues;
lpAdrList->aEntries[0].rgPropVals = lpSubscriberRow->lpProps;
// Remove the subscriber from intro msg and memory buffer.
hr = STopic_HrModifySubscriberList( lpTopic, lpAdrList, MODRECIP_REMOVE);
if( FAILED( hr))
goto cleanup;
cleanup:
// *************** W A R N I N G ! **************
// Do NOT use FREEPADRLIST on this since it points to data (lpSubscriberRow->lpProps)
// that will be freed in another way (or already has been freed).
MAPIFREEBUFFER( lpAdrList);
// *************** W A R N I N G ! **************
RETURN( hr);
}
//$--STopic_GetCopyOfSubscriberRowSet-------------------------------------------
// Returns a copy of the subscriber's row set as andress list this MUST be freed
// using FREEPROWS when the user is done with it.
// -----------------------------------------------------------------------------
HRESULT STopic_GetCopyOfSubscriberRowSet(
THIS STopic* lpTopic, // The topic object we are working on.
OUT LPADRLIST* lppAdrList) // Ptr to LPADRLIST that we will return.
{
HRESULT hr = NOERROR;
LPADRENTRY lpAdrEntry = NULL; // Destination.
LPSRow lpRow = NULL; // Source.
LPSRow lpLastRow = NULL;
LPSPropValue lpProps = NULL;
ULONG cRows = 0;
ULONG cBytes = 0;
ULONG cBytesCopied= 0;
DEBUGPUBLIC( "STopic_GetCopyOfSubscriberRowSet()");
hr = CHK_STopic_GetCopyOfSubscriberRowSet( lpTopic, lppAdrList);
if( FAILED( hr))
RETURN( hr);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Alocate enough memory for an address list to hold all subscribers.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cRows = lpTopic->lpSubscriberRowSet->cRows;
cBytes = CbNewADRLIST( cRows);
hr = MAPIAllocateBuffer( cBytes, lppAdrList);
if( FAILED( hr))
goto cleanup;
memset( *lppAdrList, 0, cBytes);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Loop through each subscriber row and copy the properties to a new buffer and
// a pointer to the new buffer into the address list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
lpAdrEntry = (*lppAdrList)->aEntries; // Destination.
lpRow = lpTopic->lpSubscriberRowSet->aRow; // Source.
lpLastRow = lpRow + (cRows - 1);
while( lpRow <= lpLastRow)
{
// Count the bytes needed for the new property array.
hr = ScCountProps( lpRow->cValues, lpRow->lpProps, &cBytes);
if( FAILED( hr))
goto cleanup;
// Allocate a new buffer to hold the copied properties.
hr = MAPIAllocateBuffer( cBytes, &lpProps);
if( FAILED( hr))
goto cleanup;
memset( lpProps, 0, cBytes);
// Copy the subscriber properties to the new buffer.
hr = ScCopyProps( lpRow->cValues, lpRow->lpProps, lpProps, &cBytesCopied);
if( FAILED( hr))
goto cleanup;
ASSERTERROR( cBytes == cBytesCopied, "ScCountProps & ScCopyProps are inconsistent!");
// Set a single address list entry.
(*lppAdrList)->cEntries ++;
lpAdrEntry->cValues = lpRow->cValues;
lpAdrEntry->rgPropVals = lpProps;
lpProps = NULL; // Just incase we bail out.
// Move on to the next subscriber.
lpRow ++;
lpAdrEntry ++;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cleanup:
if( FAILED( hr))
FREEPADRLIST( *lppAdrList);
return( hr);
}
// -----------------------------------------------------------------------------