|
|
// File: GAL.cpp
#include "precomp.h"
#include "resource.h"
#include "help_ids.h"
#include "dirutil.h"
#include "GAL.h"
#include "MapiInit.h"
#include "AdLkup.h"
#include <lst.h>
#define NM_INVALID_MAPI_PROPERTY 0
// Registry Stuff
/* static */ LPCTSTR CGAL::msc_szDefaultILSServerRegKey = ISAPI_CLIENT_KEY; /* static */ LPCTSTR CGAL::msc_szDefaultILSServerValue = REGVAL_SERVERNAME; /* static */ LPCTSTR CGAL::msc_szNMPolRegKey = POLICIES_KEY; /* static */ LPCTSTR CGAL::msc_szNMExchangeAtrValue = REGVAL_POL_NMADDRPROP; /* static */ LPCTSTR CGAL::msc_szSMTPADDRESSNAME = TEXT( "SMTP" );
// If there is no DISPLAY_NAME or ACCOUNT_NAME, don't display anything
/* static */ LPCTSTR CGAL::msc_szNoDisplayName = TEXT( "" ); /* static */ LPCTSTR CGAL::msc_szNoEMailName = TEXT( "" ); /* static */ LPCTSTR CGAL::msc_szNoBusinessTelephoneNum = TEXT( "" );
// Async stuff - there is only one instance of the GAL thread
/* static */ HINSTANCE CGAL::m_hInstMapi32DLL = NULL; /* static */ HANDLE CGAL::m_hEventEndAsyncThread = NULL; /* static */ HANDLE CGAL::m_hAsyncLogOntoGalThread = NULL; /* static */ CGAL::eAsyncLogonState CGAL::m_AsyncLogonState = CGAL::AsyncLogonState_Idle;
/* static */ IAddrBook * CGAL::m_pAddrBook = NULL; /* static */ IMAPITable * CGAL::m_pContentsTable = NULL; /* static */ IMAPIContainer * CGAL::m_pGAL = NULL; /* static */ ULONG CGAL::m_nRows = 0;
static const int _rgIdMenu[] = { IDM_DLGCALL_SPEEDDIAL, 0 };
CGAL::CGAL() : CALV(IDS_DLGCALL_GAL, II_GAL, _rgIdMenu, true ), m_nBlockSize( DefaultBlockSize ), m_MaxCacheSize( DefaultMaxCacheSize ), m_bBeginningBookmarkIsValid( false ), m_bEndBookmarkIsValid( false ), m_hrGALError( S_OK ), m_hWndListView(NULL) { DbgMsg(iZONE_OBJECTS, "CGAL - Constructed(%08X)", this);
_ResetCache();
msc_ErrorEntry_NoGAL = CGalEntry();
if (NULL == m_hInstMapi32DLL) { WARNING_OUT(("MAPI32.dll was not loaded?")); return; }
//////////////////////////////////////////////////////////////////////////////////////////
// We have to see if the GAL is available..
// this is modified from Q188482 and Q171636
//////////////////////////////////////////////////////////////////////////////////////////
// first we have to initialize MAPI for this ( the main ) thread...
MAPIINIT_0 mi = { MAPI_INIT_VERSION, MAPI_MULTITHREAD_NOTIFICATIONS }; TRACE_OUT(("Initializing MAPI")); HRESULT hr = lpfnMAPIInitialize(&mi); if( SUCCEEDED( hr ) ) { TRACE_OUT(("MAPI Initialized"));
// We have to get a pointer to the AdminProfile which is basically
// a manipulator for the mapisvc.inf file that should be on user's computer
LPPROFADMIN pAdminProfiles = NULL; hr = lpfnMAPIAdminProfiles( 0L, &pAdminProfiles );
if( SUCCEEDED( hr ) ) { ASSERT( pAdminProfiles );
// Get the profile table to search for the default profile
LPMAPITABLE pProfTable = NULL; hr = pAdminProfiles->GetProfileTable( 0L, &pProfTable ); if( SUCCEEDED( hr ) ) { ASSERT( pProfTable );
// Set the restriction to search for the default profile
SRestriction Restriction; SPropValue spv; Restriction.rt = RES_PROPERTY; Restriction.res.resProperty.relop = RELOP_EQ; Restriction.res.resProperty.ulPropTag = PR_DEFAULT_PROFILE; Restriction.res.resProperty.lpProp = &spv; spv.ulPropTag = PR_DEFAULT_PROFILE; spv.Value.b = TRUE;
// Find the default profile....
hr = pProfTable->FindRow( &Restriction, BOOKMARK_BEGINNING, 0 ); if( SUCCEEDED( hr ) ) { // We have a default profile
LPSRowSet pRow = NULL; hr = pProfTable->QueryRows( 1, 0, &pRow ); if( SUCCEEDED( hr ) ) { ASSERT( pRow );
// The profile table entry really should have only two properties,
// We will simply enumerate the proprtiies instead of hard-coding the
// order of the properties ( in case it changes in the future )
// PR_DISPLAY_NAME and PR_DEFAULT_PROFILE
for( UINT iCur = 0; iCur < pRow->aRow->cValues; ++iCur ) { // We are only interested in the PR_DISPLAY_NAME property
if( pRow->aRow->lpProps[iCur].ulPropTag == PR_DISPLAY_NAME ) { // Now that we have the default profile, we want to get the
// profile admin interface for this profile
LPSERVICEADMIN pSvcAdmin = NULL; // Pointer to IServiceAdmin object
hr = pAdminProfiles->AdminServices( pRow->aRow->lpProps[iCur].Value.LPSZ, NULL, 0L, 0L, &pSvcAdmin ); if( SUCCEEDED( hr ) ) { ASSERT( pSvcAdmin );
LPMAPITABLE pSvcTable = NULL; if( SUCCEEDED( hr = pSvcAdmin->GetMsgServiceTable( 0L, &pSvcTable ) ) ) { ASSERT( pSvcTable );
enum {iSvcName, iSvcUID, cptaSvc}; SizedSPropTagArray (cptaSvc, sptCols) = { cptaSvc, PR_SERVICE_NAME, PR_SERVICE_UID };
Restriction.rt = RES_PROPERTY; Restriction.res.resProperty.relop = RELOP_EQ; Restriction.res.resProperty.ulPropTag = PR_SERVICE_NAME; Restriction.res.resProperty.lpProp = &spv; spv.ulPropTag = PR_SERVICE_NAME; spv.Value.LPSZ = _T("MSEMS");
LPSRowSet pRowExch = NULL; if ( SUCCEEDED( hr = lpfnHrQueryAllRows( pSvcTable, (LPSPropTagArray)&sptCols, &Restriction, NULL, 0, &pRowExch ) ) ) { SetAvailable(TRUE); lpfnFreeProws( pRowExch ); iCur = pRow->aRow->cValues; }
pSvcTable->Release(); pSvcTable = NULL; }
pSvcAdmin->Release(); pSvcAdmin = NULL; } } }
lpfnFreeProws( pRow ); } }
pProfTable->Release(); pProfTable = NULL; }
pAdminProfiles->Release(); pAdminProfiles = NULL; }
lpfnMAPIUninitialize(); } m_MaxJumpSize = m_nBlockSize; }
CGAL::~CGAL() { // Kill the cache
_ResetCache();
DbgMsg(iZONE_OBJECTS, "CGAL - Destroyed(%08X)", this); }
// static function to load MAPI32.dll
BOOL CGAL::FLoadMapiFns(void) { if (NULL != m_hInstMapi32DLL) return TRUE;
return LoadMapiFns(&m_hInstMapi32DLL); }
// static function to unload MAPI32.dll and logoff, if necessary
VOID CGAL::UnloadMapiFns(void) { if (NULL != m_hAsyncLogOntoGalThread) { TRACE_OUT(("Setting AsyncLogOntoGalThread End Event")); ASSERT(NULL != m_hEventEndAsyncThread); SetEvent(m_hEventEndAsyncThread);
WARNING_OUT(("Waiting for AsyncLogOntoGalThread to exit (start)")); WaitForSingleObject(m_hAsyncLogOntoGalThread, 30000); // 30 seconds max
WARNING_OUT(("Waiting for AsyncLogOntoGalThread to exit (end)"));
CloseHandle(m_hAsyncLogOntoGalThread); m_hAsyncLogOntoGalThread = NULL;
CloseHandle(m_hEventEndAsyncThread); m_hEventEndAsyncThread = NULL; }
if (NULL != m_hInstMapi32DLL) { FreeLibrary(m_hInstMapi32DLL); m_hInstMapi32DLL = NULL; } }
/* virtual */ int CGAL::OnListGetImageForItem( int iIndex ) {
if( !_IsLoggedOn() ) { return II_INVALIDINDEX; }
CGalEntry* pEntry = _GetEntry( iIndex ); if( pEntry->GetDisplayType() == DT_MAILUSER ) { return II_INVALIDINDEX; }
switch( pEntry->GetDisplayType() ) { case DT_DISTLIST: return II_DISTLIST; case DT_FORUM: return II_FORUM; case DT_AGENT: return II_AGENT; case DT_ORGANIZATION: return II_ORGANIZATION; case DT_PRIVATE_DISTLIST: return II_PRIVATE_DISTLIST; case DT_REMOTE_MAILUSER: return II_REMOTE_MAILUSER;
default: ERROR_OUT(("We have an invalid Display Type")); return II_INVALIDINDEX; }
return II_INVALIDINDEX;
}
/* virtual */ bool CGAL::IsItemBold( int index ) {
if( !_IsLoggedOn() ) { return false; }
CGalEntry* pEntry = _GetEntry( index );
switch( pEntry->GetDisplayType() ) { case DT_DISTLIST: case DT_PRIVATE_DISTLIST: return true; case DT_MAILUSER: case DT_FORUM: case DT_AGENT: case DT_ORGANIZATION: case DT_REMOTE_MAILUSER: return false;
default: ERROR_OUT(("Invalid DT in CGAL::IsItemBold")); return false; }
return false;
}
HRESULT CGAL::_GetEmailNames( int* pnEmailNames, LPTSTR** ppszEmailNames, int iItem ) { HRESULT hr = S_OK; *pnEmailNames = 1; *ppszEmailNames = new LPTSTR[1]; (*ppszEmailNames)[0] = NULL; CGalEntry* pCurSel = _GetItemFromCache( iItem ); if( pCurSel ) { (*ppszEmailNames)[0] = PszAlloc( pCurSel->GetEMail() ); }
return hr; }
/* virtual */ RAI * CGAL::GetAddrInfo(void) {
RAI* pRai = NULL;
int iItem = GetSelection();
if (-1 != iItem) { HWND hwnd = GetHwnd(); LPTSTR* pszPhoneNums = NULL; LPTSTR* pszEmailNames = NULL; int nPhoneNums = 0; int nEmailNames = 0;
CGalEntry* pCurSel = _GetItemFromCache( iItem );
if( g_fGkEnabled ) { if( g_bGkPhoneNumberAddressing ) { _GetPhoneNumbers( pCurSel->GetInstanceKey(), &nPhoneNums, &pszPhoneNums ); } else { _GetEmailNames( &nEmailNames, &pszEmailNames, iItem ); } } else { // This is regular call placement mode
if( g_fGatewayEnabled ) { _GetPhoneNumbers( pCurSel->GetInstanceKey(), &nPhoneNums, &pszPhoneNums ); }
nEmailNames = 1; pszEmailNames = new LPTSTR[1]; pszEmailNames[0] = new TCHAR[CCHMAXSZ]; GetSzAddress( pszEmailNames[0], CCHMAXSZ, iItem ); }
if( nPhoneNums || nEmailNames ) {
int nItems = nPhoneNums + nEmailNames; DWORD cbLen = sizeof(RAI) + sizeof(DWSTR)* nItems; pRai = reinterpret_cast<RAI*>(new BYTE[ cbLen ]); ZeroMemory(pRai, cbLen); pRai->cItems = nItems;
int iCur = 0; lstrcpyn( pRai->szName, pCurSel->GetName(), CCHMAX(pRai->szName) );
// First copy the e-mail names
for( int i = 0; i < nEmailNames; i++ ) { DWORD dwAddressType = g_fGkEnabled ? NM_ADDR_ALIAS_ID : NM_ADDR_ULS; pRai->rgDwStr[iCur].dw = dwAddressType; pRai->rgDwStr[iCur].psz = pszEmailNames[i]; ++iCur; } delete [] pszEmailNames;
// Copy the phone numbirs
for( i = 0; i < nPhoneNums; i++ ) { pRai->rgDwStr[iCur].dw = g_fGkEnabled ? NM_ADDR_ALIAS_E164 : NM_ADDR_H323_GATEWAY; pRai->rgDwStr[iCur].psz = pszPhoneNums[i]; ++iCur; } delete [] pszPhoneNums; }
}
return pRai;
}
HRESULT CGAL::_GetPhoneNumbers( const SBinary& rEntryID, int* pcPhoneNumbers, LPTSTR** ppszPhoneNums ) { HRESULT hr = S_OK;
if( pcPhoneNumbers && ppszPhoneNums ) { *pcPhoneNumbers = 0; *ppszPhoneNums = NULL;
ULONG PhoneNumPropTags[] = { PR_BUSINESS_TELEPHONE_NUMBER, PR_HOME_TELEPHONE_NUMBER, PR_PRIMARY_TELEPHONE_NUMBER, PR_BUSINESS2_TELEPHONE_NUMBER, PR_CELLULAR_TELEPHONE_NUMBER, PR_RADIO_TELEPHONE_NUMBER, PR_CAR_TELEPHONE_NUMBER, PR_OTHER_TELEPHONE_NUMBER, PR_PAGER_TELEPHONE_NUMBER };
BYTE* pb = new BYTE[ sizeof( SPropTagArray ) + sizeof( ULONG ) * ARRAY_ELEMENTS(PhoneNumPropTags) ];
if( pb ) { SPropTagArray* pta = reinterpret_cast<SPropTagArray*>(pb);
pta->cValues = ARRAY_ELEMENTS(PhoneNumPropTags);
for( UINT iCur = 0; iCur < pta->cValues; iCur++ ) { pta->aulPropTag[iCur] = PhoneNumPropTags[iCur]; }
hr = m_pContentsTable->SetColumns(pta, TBL_BATCH); if (SUCCEEDED(hr)) { if( SUCCEEDED( hr = _SetCursorTo( rEntryID ) ) ) { LPSRowSet pRow; // Get the item from the GAL
if ( SUCCEEDED ( hr = m_pContentsTable->QueryRows( 1, TBL_NOADVANCE, &pRow ) ) ) { lst<LPTSTR> PhoneNums;
// First we have to find out how many nums there are
for( UINT iCur = 0; iCur < pRow->aRow->cValues; ++iCur ) { if( LOWORD( pRow->aRow->lpProps[iCur].ulPropTag ) != PT_ERROR ) { TCHAR szExtractedAddress[CCHMAXSZ];
DWORD dwAddrType = g_fGkEnabled ? NM_ADDR_ALIAS_E164 : NM_ADDR_H323_GATEWAY; ExtractAddress( dwAddrType, #ifdef UNICODE
pRow->aRow->lpProps[iCur].Value.lpszW, #else
pRow->aRow->lpProps[iCur].Value.lpszA, #endif // UNICODE
szExtractedAddress, CCHMAX(szExtractedAddress) );
if( IsValidAddress( dwAddrType, szExtractedAddress ) ) { ++(*pcPhoneNumbers); PhoneNums.push_back(PszAlloc( #ifdef UNICODE
pRow->aRow->lpProps[iCur].Value.lpszW #else
pRow->aRow->lpProps[iCur].Value.lpszA #endif // UNICODE
) ); } } } *ppszPhoneNums = new LPTSTR[ PhoneNums.size() ]; if( *ppszPhoneNums ) { lst<LPTSTR>::iterator I = PhoneNums.begin(); int iCur = 0; while( I != PhoneNums.end() ) { *ppszPhoneNums[iCur] = *I; ++iCur, ++I; } } else { hr = E_OUTOFMEMORY; }
lpfnFreeProws( pRow ); } } else { hr = E_OUTOFMEMORY; } }
delete [] pb; } else { hr = E_OUTOFMEMORY; } } else { hr = E_POINTER; } return hr; }
/* virtual */ void CGAL::OnListCacheHint( int indexFrom, int indexTo ) {
if( !_IsLoggedOn() ) { return; } // TRACE_OUT(("OnListCacheHint( %d, %d )", indexFrom, indexTo ));
}
/* virtual */ VOID CGAL::CmdProperties( void ) {
int iItem = GetSelection(); if (-1 == iItem) { return; }
HRESULT hr; HWND hwnd = GetHwnd();
CGalEntry* pCurSel = _GetItemFromCache( iItem );
const SBinary& rEntryID = pCurSel->GetEntryID();
ULONG ulFlags = DIALOG_MODAL;
#ifdef UNICODE
ulFlags |= MAPI_UNICODE; #endif // UNICODE
hr = m_pAddrBook->Details( reinterpret_cast< LPULONG >( &hwnd ), NULL, NULL, rEntryID.cb, reinterpret_cast< LPENTRYID >( rEntryID.lpb ), NULL, NULL, NULL, ulFlags ); }
// This is called when the Global Address List item is selected from the
// combo box in the call dialog
/* virtual */ VOID CGAL::ShowItems(HWND hwnd) { CALV::SetHeader(hwnd, IDS_ADDRESS); ListView_SetItemCount(hwnd, 0); m_hWndListView = hwnd;
if(SUCCEEDED(m_hrGALError)) { TCHAR szPhoneNumber[CCHMAXSZ]; if( FLoadString(IDS_PHONENUM, szPhoneNumber, CCHMAX(szPhoneNumber)) ) { LV_COLUMN lvc; ClearStruct(&lvc); lvc.mask = LVCF_TEXT | LVCF_SUBITEM; lvc.pszText = szPhoneNumber; lvc.iSubItem = 2; ListView_InsertColumn(hwnd, IDI_DLGCALL_PHONENUM, &lvc); }
m_MaxCacheSize = ListView_GetCountPerPage(hwnd) * NUM_LISTVIEW_PAGES_IN_CACHE; if (m_MaxCacheSize < m_MaxJumpSize) { // The cache has to be at least as big as the jump size
m_MaxCacheSize = m_MaxJumpSize * 2; }
if (!_IsLoggedOn()) { _AsyncLogOntoGAL(); } else { _sInitListViewAndGalColumns(hwnd); } } }
/* C L E A R I T E M S */ /*-------------------------------------------------------------------------
%%Function: ClearItems -------------------------------------------------------------------------*/ VOID CGAL::ClearItems(void) { CALV::ClearItems();
if( IsWindow(m_hWndListView) ) { ListView_DeleteColumn(m_hWndListView, IDI_DLGCALL_PHONENUM); } else { WARNING_OUT(("m_hWndListView is not valid in CGAL::ClearItems")); } }
/* _ S I N I T L I S T V I E W A N D G A L C O L U M N S */ /*-------------------------------------------------------------------------
%%Function: _sInitListViewAndGalColumns -------------------------------------------------------------------------*/ HRESULT CGAL::_sInitListViewAndGalColumns(HWND hwnd) { // Set the GAL columns before we let the listview try to get the data
struct SPropTagArray_sptCols { ULONG cValues; ULONG aulPropTag[ NUM_PROPS ]; } sptCols;
sptCols.cValues = NUM_PROPS; sptCols.aulPropTag[ NAME_PROP_INDEX ] = PR_DISPLAY_NAME; sptCols.aulPropTag[ ACCOUNT_PROP_INDEX ] = PR_ACCOUNT; sptCols.aulPropTag[ INSTANCEKEY_PROP_INDEX ] = PR_INSTANCE_KEY; sptCols.aulPropTag[ ENTRYID_PROP_INDEX ] = PR_ENTRYID; sptCols.aulPropTag[ DISPLAY_TYPE_INDEX ] = PR_DISPLAY_TYPE; sptCols.aulPropTag[ BUSINESS_PHONE_NUM_PROP_INDEX ] = PR_BUSINESS_TELEPHONE_NUMBER;
HRESULT hr = m_pContentsTable->SetColumns((LPSPropTagArray) &sptCols, TBL_BATCH); if (SUCCEEDED(hr)) { // Get the row count so we can initialize the OWNER DATA ListView
hr = m_pContentsTable->GetRowCount(0, &m_nRows); if (SUCCEEDED(hr)) { // Set the list view size to the number of entries in the GAL
ListView_SetItemCount(hwnd, m_nRows); } }
return hr; }
/* _ A S Y N C L O G O N T O G A L */ /*-------------------------------------------------------------------------
%%Function: _AsyncLogOntoGAL -------------------------------------------------------------------------*/ HRESULT CGAL::_AsyncLogOntoGAL(void) {
if ((AsyncLogonState_Idle != m_AsyncLogonState) || (NULL != m_hAsyncLogOntoGalThread)) { return S_FALSE; }
m_AsyncLogonState = AsyncLogonState_LoggingOn; ASSERT(NULL == m_hEventEndAsyncThread); m_hEventEndAsyncThread = CreateEvent(NULL, TRUE, FALSE, NULL);
DWORD dwThID; TRACE_OUT(("Creating AsyncLogOntoGal Thread")); m_hAsyncLogOntoGalThread = CreateThread(NULL, 0, _sAsyncLogOntoGalThreadfn, static_cast< LPVOID >(GetHwnd()), 0, &dwThID);
if (NULL == m_hAsyncLogOntoGalThread) { m_AsyncLogonState = AsyncLogonState_Idle; return HRESULT_FROM_WIN32(GetLastError()); }
return S_OK; }
/* static */ DWORD CALLBACK CGAL::_sAsyncLogOntoGalThreadfn(LPVOID pv) { SetBusyCursor(TRUE); HRESULT hr = _sAsyncLogOntoGal(); SetBusyCursor(FALSE);
if (S_OK == hr) { TRACE_OUT(("in _AsyncLogOntoGalThreadfn: Calling _InitListViewAndGalColumns")); _sInitListViewAndGalColumns((HWND) pv);
// This keeps the thread around until we're done
WaitForSingleObject(m_hEventEndAsyncThread, INFINITE); }
// Clean up in the same thread
hr = _sAsyncLogoffGal();
return (DWORD) hr; }
/* static */ HRESULT CGAL::_sAsyncLogOntoGal(void) { ULONG cbeid = 0L; LPENTRYID lpeid = NULL; HRESULT hr = S_OK; ULONG ulObjType;
MAPIINIT_0 mi = { MAPI_INIT_VERSION, MAPI_MULTITHREAD_NOTIFICATIONS };
TRACE_OUT(("in _AsyncLogOntoGalThreadfn: Calling MAPIInitialize"));
hr = lpfnMAPIInitialize(&mi); if (FAILED(hr)) return hr;
TRACE_OUT(("in _AsyncLogOntoGalThreadfn: Calling MAPILogonEx")); IMAPISession* pMapiSession; hr = lpfnMAPILogonEx( NULL, NULL, NULL, MAPI_EXTENDED | MAPI_USE_DEFAULT, &pMapiSession ); if (FAILED(hr)) return hr;
// Open the main address book
TRACE_OUT(("in _AsyncLogOntoGalThreadfn: Calling OpenAddressBook")); ASSERT(NULL == m_pAddrBook); hr = pMapiSession->OpenAddressBook(NULL, NULL, AB_NO_DIALOG, &m_pAddrBook);
pMapiSession->Release(); pMapiSession = NULL;
if (FAILED(hr)) return hr;
TRACE_OUT(("in _AsyncLogOntoGalThreadfn: Calling HrFindExchangeGlobalAddressList ")); hr = HrFindExchangeGlobalAddressList(m_pAddrBook, &cbeid, &lpeid); if (FAILED(hr)) return hr;
TRACE_OUT(("in _AsyncLogOntoGalThreadfn: Calling OpenEntry")); ASSERT(NULL == m_pGAL); hr = m_pAddrBook->OpenEntry(cbeid, lpeid, NULL, MAPI_BEST_ACCESS, &ulObjType, reinterpret_cast< IUnknown** >( &m_pGAL)); if (FAILED(hr)) return hr;
if (ulObjType != MAPI_ABCONT) return GAL_E_GAL_NOT_FOUND;
TRACE_OUT(("in _AsyncLogOntoGalThreadfn: Calling GetContentsTable")); ASSERT(NULL == m_pContentsTable); hr = m_pGAL->GetContentsTable(0L, &m_pContentsTable); if (FAILED(hr)) return hr;
m_AsyncLogonState = AsyncLogonState_LoggedOn;
return hr; } /* static */ HRESULT CGAL::_sAsyncLogoffGal(void) { // Free and release all the stuff that we hold onto
TRACE_OUT(("in _AsyncLogOntoGalThreadfn: Releasing MAPI Interfaces"));
if (NULL != m_pContentsTable) { m_pContentsTable->Release(); m_pContentsTable = NULL; } if (NULL != m_pAddrBook) { m_pAddrBook->Release(); m_pAddrBook = NULL; } if (NULL != m_pGAL) { m_pGAL->Release(); m_pGAL = NULL; }
WARNING_OUT(("in _AsyncLogOntoGalThreadfn: Calling lpfnMAPIUninitialize")); lpfnMAPIUninitialize();
m_AsyncLogonState = AsyncLogonState_Idle; return S_OK; }
HRESULT CGAL::_SetCursorTo( const CGalEntry& rEntry ) { return _SetCursorTo( rEntry.GetInstanceKey() ); }
HRESULT CGAL::_SetCursorTo( LPCTSTR szPartialMatch ) {
// Find the row that matches the partial String based on the DISPLAY_NAME;
SRestriction Restriction; SPropValue spv; Restriction.rt = RES_PROPERTY; Restriction.res.resProperty.relop = RELOP_GE; Restriction.res.resProperty.lpProp = &spv; Restriction.res.resProperty.ulPropTag = PR_DISPLAY_NAME; spv.ulPropTag = PR_DISPLAY_NAME;
#ifdef UNICODE
spv.Value.lpszW = const_cast< LPTSTR >( szPartialMatch ); #else
spv.Value.lpszA = const_cast< LPTSTR >( szPartialMatch ); #endif // UNICODE
// Find the first row that is lexographically greater than or equal to the search string
HRESULT hr = m_pContentsTable->FindRow( &Restriction, BOOKMARK_BEGINNING, 0 ); if( FAILED( hr ) ) { if( MAPI_E_NOT_FOUND == hr ) { // This is not really an error, because we handle it from the calling
// function. That is, we don't have to set m_hrGALError here...
return MAPI_E_NOT_FOUND; }
m_hrGALError = GAL_E_QUERYROWS_FAILED; return GAL_E_QUERYROWS_FAILED; }
return S_OK; }
HRESULT CGAL::_SetCursorTo( const SBinary& rInstanceKey ) {
HRESULT hr;
// there is an exchange reg key, we have to get the user's data from the GAL
SRestriction Restriction; SPropValue spv;
// Search for the user using the instance key data that is in the CGalEntry for the currently
// selected list box item
Restriction.rt = RES_PROPERTY; Restriction.res.resProperty.relop = RELOP_EQ; Restriction.res.resProperty.ulPropTag = PR_INSTANCE_KEY; Restriction.res.resProperty.lpProp = &spv;
spv.ulPropTag = PR_INSTANCE_KEY;
// Get the INSTANCE_KEY from the cache
spv.Value.bin.cb = rInstanceKey.cb; spv.Value.bin.lpb = new byte[ spv.Value.bin.cb ]; ASSERT( spv.Value.bin.cb ); memcpy( spv.Value.bin.lpb, rInstanceKey.lpb, spv.Value.bin.cb );
// find the user in the table...
hr = m_pContentsTable->FindRow( &Restriction, BOOKMARK_BEGINNING, 0 );
if( FAILED( hr ) ) { m_hrGALError = GAL_E_FINDROW_FAILED; delete [] ( spv.Value.bin.lpb ); return GAL_E_FINDROW_FAILED; }
delete [] ( spv.Value.bin.lpb ); return S_OK; }
bool CGAL::_GetSzAddressFromExchangeServer( int iItem, LPTSTR psz, int cchMax ) { HRESULT hr;
// In the registry, there may be a key that says what the MAPI attribute
// is in which the users' ILS server is stored in the GAL... If the
// reg key exists, we have to get the property from the GAL
DWORD dwAttr = _GetExchangeAttribute( ); bool bExtensionFound = ( NM_INVALID_MAPI_PROPERTY != dwAttr );
// Re-create the table so that it includes the MAPI property tag found in the EXCHANGE REG ATTRUBITE
SizedSPropTagArray( 3, sptColsExtensionFound ) = { 3, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PROP_TAG( PT_TSTRING, dwAttr) }; SizedSPropTagArray( 2, sptColsExtensionNotFound ) = { 2, PR_EMAIL_ADDRESS, PR_ADDRTYPE }; const int EmailPropertyIndex = 0; const int EmailAddressTypePropertyIndex = 1; const int ExtensionPropertyIndex = 2; if( bExtensionFound ) { if(FAILED(hr = m_pContentsTable->SetColumns( ( LPSPropTagArray ) &sptColsExtensionFound, TBL_BATCH ) ) ) { m_hrGALError = GAL_E_SETCOLUMNS_FAILED; return false; } } else { if(FAILED(hr = m_pContentsTable->SetColumns( ( LPSPropTagArray ) &sptColsExtensionNotFound, TBL_BATCH ) ) ) { m_hrGALError = GAL_E_SETCOLUMNS_FAILED; return false; } }
if( FAILED( hr = _SetCursorTo( *_GetItemFromCache( iItem ) ) ) ) { return false; }
LPSRowSet pRow; // Get the item from the GAL
if ( SUCCEEDED ( hr = m_pContentsTable->QueryRows( 1, TBL_NOADVANCE, &pRow ) ) ) { if( bExtensionFound ) { // Copy the extension data from the entry if it is there
if( LOWORD( pRow->aRow->lpProps[ ExtensionPropertyIndex ].ulPropTag ) != PT_ERROR ) { TRACE_OUT(("Using custom Exchange data for address")); _CopyPropertyString( psz, pRow->aRow->lpProps[ ExtensionPropertyIndex ], cchMax ); lpfnFreeProws( pRow ); return true; } } // If the extension was not found in the reg, or if there was no extension data.
// use the e-mail address if it is SMTP type...
if( LOWORD( pRow->aRow->lpProps[ EmailAddressTypePropertyIndex ].ulPropTag ) != PT_ERROR ) { // Check to see if the address type is SMTP
#ifdef UNICODE
TRACE_OUT(("Email address %s:%s", pRow->aRow->lpProps[ EmailAddressTypePropertyIndex ].Value.lpszW, pRow->aRow->lpProps[ EmailPropertyIndex ].Value.lpszW )); if( !lstrcmp( msc_szSMTPADDRESSNAME, pRow->aRow->lpProps[ EmailAddressTypePropertyIndex ].Value.lpszW ) ) { #else
TRACE_OUT(("Email address %s:%s", pRow->aRow->lpProps[ EmailAddressTypePropertyIndex ].Value.lpszA, pRow->aRow->lpProps[ EmailPropertyIndex ].Value.lpszA )); if( !lstrcmp( msc_szSMTPADDRESSNAME, pRow->aRow->lpProps[ EmailAddressTypePropertyIndex ].Value.lpszA ) ) { #endif // UNICODE
TRACE_OUT(("Using SMTP E-mail as address")); if( LOWORD( pRow->aRow->lpProps[ EmailPropertyIndex ].ulPropTag ) != PT_ERROR ) { FGetDefaultServer( psz, cchMax - 1 ); int ServerPrefixLen = lstrlen( psz ); psz[ ServerPrefixLen ] = TEXT( '/' ); ++ServerPrefixLen; ASSERT( ServerPrefixLen < cchMax ); _CopyPropertyString( psz + ServerPrefixLen, pRow->aRow->lpProps[ EmailPropertyIndex ], cchMax - ServerPrefixLen ); lpfnFreeProws( pRow ); return true; } } }
lpfnFreeProws( pRow ); } else { m_hrGALError = GAL_E_QUERYROWS_FAILED; return false; }
// This means that we did not find the data on the server
return false; }
void CGAL::_CopyPropertyString( LPTSTR psz, SPropValue& rProp, int cchMax ) {
#ifdef UNICODE
lstrcpyn( psz, rProp.Value.lpszW, cchMax ); #else
lstrcpyn( psz, rProp.Value.lpszA, cchMax ); #endif // UNICODE
}
// When the user selects CALL, we have to
// Create an address for them to callto://
BOOL CGAL::GetSzAddress(LPTSTR psz, int cchMax, int iItem) { // try and get the data from the exchange server as per the spec...
if (_GetSzAddressFromExchangeServer(iItem, psz, cchMax)) { TRACE_OUT(("CGAL::GetSzAddress() returning address [%s]", psz)); return TRUE; }
// If the data is not on the server, we are going to create the address in the format
// <default_server>/<PR_ACCOUNT string>
if (!FGetDefaultServer(psz, cchMax - 1)) return FALSE;
// Because the syntax is callto:<servername>/<username>
// we have to put in the forward-slash
int cch = lstrlen(psz); psz[cch++] = '/'; psz += cch; cchMax -= cch;
// There was no data on the server for us, so we will just use the PR_ACCOUNT data that we have cached
return CALV::GetSzData(psz, cchMax, iItem, IDI_DLGCALL_ADDRESS); }
// When the user types in a search string ( partial match string ) in the edit box
// above the ListView, we want to show them the entries starting with the given string
ULONG CGAL::OnListFindItem( LPCTSTR szPartialMatchingString ) {
if( !_IsLoggedOn() ) { return 0; }
// If we have such an item cached, return the index to it
int index; if( -1 != ( index = _FindItemInCache( szPartialMatchingString ) ) ) { return index; } // if the edit box is empty ( NULL string ), then we know to return item 0
if( szPartialMatchingString[ 0 ] == '\0' ) { return 0; } HRESULT hr; if( FAILED( hr = _SetCursorTo( szPartialMatchingString ) ) ) { if( MAPI_E_NOT_FOUND == hr ) { return m_nRows - 1; } return 0; }
// We have to find the row number of the cursor where the partial match is...
ULONG ulRow, ulPositionNumerator, ulPositionDenominator; m_pContentsTable->QueryPosition( &ulRow, &ulPositionNumerator, &ulPositionDenominator ); if( ulRow == 0xFFFFFFFF ) { // If QueryPosition is unable to determine the ROW, it will return the row based on the
// fraction ulPositionNumerator/ulPositionDenominator
ulRow = MulDiv( m_nRows, ulPositionNumerator, ulPositionDenominator ); }
// Kill the cache, becasue we are jumping to a new block of data
// We do this because the _FindItemInCache call above failed to
// return the desired item...
_ResetCache(); m_IndexOfFirstItemInCache = ulRow; m_IndexOfLastItemInCache = ulRow - 1; // Jump back a few, so we can cache some entries before the one we are looking for
long lJumped; hr = m_pContentsTable->SeekRow( BOOKMARK_CURRENT, -( m_nBlockSize / 2 ), &lJumped ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_SEEKROW_FAILED; return 0; }
// We hawe to change the sign of lJumped because we are jumping backwards
lJumped *= -1;
// Set the begin bookmark
hr = m_pContentsTable->CreateBookmark( &m_BookmarkOfFirstItemInCache ); ASSERT( SUCCEEDED( hr ) ); m_bBeginningBookmarkIsValid = true;
// Read in a block of rows
LPSRowSet pRow = NULL; hr = m_pContentsTable->QueryRows( m_nBlockSize, 0, &pRow );
if( FAILED( hr ) ) { m_hrGALError = GAL_E_QUERYROWS_FAILED; return 0; }
// For each item in the block
// This should always be the case,
// but we vant to make sure that we have enough rows to get to the
// item that we are looking for...
ASSERT( pRow->cRows >= static_cast< ULONG >( lJumped ) );
for( ULONG i = 0; i < pRow->cRows; i++ ) { CGalEntry* pEntry;
if( FAILED( _MakeGalEntry( pRow->aRow[ i ], &pEntry ) ) ) { lpfnFreeProws( pRow ); return 0; } if( 0 == lJumped ) { ulRow = m_IndexOfLastItemInCache + 1; }
--lJumped;
m_EntryCache.push_back( pEntry ); m_IndexOfLastItemInCache++; }
lpfnFreeProws( pRow );
// Set the end bookmark
hr = m_pContentsTable->CreateBookmark( &m_BookmarkOfItemAfterLastItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_CREATEBOOKMARK_FAILED; return 0; }
m_bEndBookmarkIsValid = true;
VERIFYCACHE
return ulRow; }
// This is called by the ListView Notification handler. Because the ListView is OWNERDATA
// it has to ask us every time it needs the string data for the columns...
void CGAL::OnListGetColumn1Data( int iItemIndex, int cchTextMax, LPTSTR szBuf ) {
if( !_IsLoggedOn() ) { lstrcpyn( szBuf, g_cszEmpty, cchTextMax ); } else { LPCTSTR pszName = _GetEntry( iItemIndex )->GetName(); if( NULL == pszName ) { pszName = g_cszEmpty; } lstrcpyn( szBuf, pszName, cchTextMax ); } }
// This is called by the ListView Notification handler. Because the ListView is OWNERDATA
// it has to ask us every time it needs the string data for the columns...
void CGAL::OnListGetColumn2Data( int iItemIndex, int cchTextMax, LPTSTR szBuf ) { if( !_IsLoggedOn() ) { lstrcpyn( szBuf, g_cszEmpty, cchTextMax ); } else {
LPCTSTR pszEMail = _GetEntry( iItemIndex )->GetEMail(); if( NULL == pszEMail ) { pszEMail = g_cszEmpty; }
lstrcpyn( szBuf, pszEMail, cchTextMax ); } }
// This is called by the ListView Notification handler. Because the ListView is OWNERDATA
// it has to ask us every time it needs the string data for the columns...
void CGAL::OnListGetColumn3Data( int iItemIndex, int cchTextMax, LPTSTR szBuf ) { if( !_IsLoggedOn() ) { lstrcpyn( szBuf, g_cszEmpty, cchTextMax ); } else { lstrcpyn( szBuf, g_cszEmpty, cchTextMax );
LPCTSTR pszBusinessTelephone = _GetEntry( iItemIndex )->GetBusinessTelephone(); if( NULL == pszBusinessTelephone ) { pszBusinessTelephone = g_cszEmpty; } lstrcpyn( szBuf, pszBusinessTelephone, cchTextMax ); } }
// When the user types in a search string in the edit box, we first check to see if there is an
// item in the cache that satisfys the partial search criteria
int CGAL::_FindItemInCache( LPCTSTR szPartialMatchString ) {
if( m_EntryCache.size() == 0 ) { return -1; } if( ( *( m_EntryCache.front() ) <= szPartialMatchString ) && ( *( m_EntryCache.back() ) >= szPartialMatchString ) ) { int index = m_IndexOfFirstItemInCache; lst< CGalEntry* >::iterator I = m_EntryCache.begin(); while( ( *( *I ) ) < szPartialMatchString ) { ++I; ++index; } return index; }
return -1; }
// _GetEntry returns a reference to the desired entry. If the entry is in the cache, it retrieves it, and if
// it is not in the cache, it loads it from the GAL and saves it in the cache
CGAL::CGalEntry* CGAL::_GetEntry( int index ) { CGalEntry* pRet = &msc_ErrorEntry_NoGAL;
if (!_IsLoggedOn() || FAILED(m_hrGALError)) { // rRet = msc_ErrorEntry_NoGAL;
} // If the entry is in the cache, return it
else if( ( index >= m_IndexOfFirstItemInCache ) && ( index <= m_IndexOfLastItemInCache ) ) { pRet = _GetItemFromCache( index ); } else if( m_EntryCache.size() == 0 ) { // If the cache is empty, LongJump
// Do a long jump to index, reset the cached data and return the item at index
pRet = _LongJumpTo( index ); } else if( ( index < m_IndexOfFirstItemInCache ) && ( ( m_IndexOfFirstItemInCache - index ) <= m_MaxJumpSize ) ) { // If index is less than the first index by less than m_MaxJumSize
// Fill in the entries below the first index and return the item at _index_
pRet = _GetEntriesAtBeginningOfList( index ); } else if( ( index > m_IndexOfLastItemInCache ) && ( ( index - m_IndexOfLastItemInCache ) <= m_MaxJumpSize ) ) { // else if index is greater than the last index by less than m_MaxJumpSize
// Fill in the entries above the last index and return the item at _index_
pRet = _GetEntriesAtEndOfList( index ); } else { // Do a long jump to index, reset the cached data and return the item at index
pRet = _LongJumpTo( index ); }
return pRet; }
// If the ListView needs an item that is far enough away from the current cache block to require a
// new cache block, this function in called. The cache is destroyed and a new cache block is
// created at the longjump item's index
CGAL::CGalEntry* CGAL::_LongJumpTo( int index ) {
HRESULT hr;
// first we have to kill the cache and free the old bookmarks because they will no longer be valid...
_ResetCache();
// Seek approximately to the spot that we are looking for...
int CacheIndex = index; int Offset = m_nBlockSize / 2;
if( CacheIndex < Offset ) { CacheIndex = 0; } else { CacheIndex -= Offset; }
hr = m_pContentsTable->SeekRowApprox( CacheIndex, m_nRows ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_SEEKROWAPPROX_FAILED; return &msc_ErrorEntry_SeekRowApproxFailed; }
m_IndexOfFirstItemInCache = CacheIndex; m_IndexOfLastItemInCache = m_IndexOfFirstItemInCache - 1;
// Set the beginningBookmark
hr = m_pContentsTable->CreateBookmark( &m_BookmarkOfFirstItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_CREATEBOOKMARK_FAILED; return &msc_ErrorEntry_CreateBookmarkFailed; }
m_bBeginningBookmarkIsValid = true;
lst< CGalEntry* >::iterator IRet = m_EntryCache.end();
// Get a block of rows
LPSRowSet pRow = NULL; hr = m_pContentsTable->QueryRows( m_nBlockSize, 0, &pRow );
if( FAILED( hr ) ) { m_hrGALError = GAL_E_QUERYROWS_FAILED; return &msc_ErrorEntry_QueryRowsFailed; }
// For each item in the block
for( ULONG i = 0; i < pRow->cRows; i++ ) {
CGalEntry* pEntry; if( FAILED( _MakeGalEntry( pRow->aRow[ i ], &pEntry ) ) ) { lpfnFreeProws( pRow ); return &msc_ErrorEntry_NoInstanceKeyFound; }
m_EntryCache.push_back( pEntry );
// if the current item is equal to the first item in our list, we are done
m_IndexOfLastItemInCache++; if( m_IndexOfLastItemInCache == index ) { IRet = --( m_EntryCache.end() ); } }
if( IRet == m_EntryCache.end() ) { // There is a small chance that this could happen
// if there were problems on the server.
WARNING_OUT(("In CGAL::_LongJumpTo(...) QueryRows only returned %u items", pRow->cRows )); WARNING_OUT(("\tm_IndexOfFirstItemInCache = %u, m_IndexOfLastItemInCache = %u, index = %u", m_IndexOfFirstItemInCache, m_IndexOfLastItemInCache, index )); m_hrGALError = GAL_E_QUERYROWS_FAILED; return &msc_ErrorEntry_QueryRowsFailed; }
lpfnFreeProws( pRow );
ASSERT( ( m_IndexOfLastItemInCache - m_IndexOfFirstItemInCache ) == static_cast< int >( m_EntryCache.size() - 1 ) );
// Set the beginningBookmark
hr = m_pContentsTable->CreateBookmark( &m_BookmarkOfItemAfterLastItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_CREATEBOOKMARK_FAILED; return &msc_ErrorEntry_CreateBookmarkFailed; } m_bEndBookmarkIsValid = true;
VERIFYCACHE
return *IRet; }
// If the user is scrolling backwards and comes to an index whose data is not in the cache, we hawe
// to get some entries at the beginning of the list... We will start at a position somewhat before the
// first item's index and keep getting items from the GAL until we have all the items up to the first item
// in the list. We continue to jump back a little and get items to the beginning of the list until we have
// cached the requested index. Because we have the item handy, we will return it
CGAL::CGalEntry* CGAL::_GetEntriesAtBeginningOfList( int index ) { HRESULT hr; // The beginning bookmark may not be valid, because the user may have been scrolling forward
// and because the cache is kept at a constant size, the item at the front bookmark may have
// been removed from the cache. If this is the case, we have to re-create the front bookmark
if( !m_bBeginningBookmarkIsValid ) { if( _CreateBeginningBookmark() ) { // This means that the listView needs to be update
ListView_RedrawItems( GetHwnd(), 0, m_nRows ); return &msc_ErrorEntry_FindRowFailed; } }
// Seek row to the beginning bookmark -m_nBlockSize items
long lJumped; hr = m_pContentsTable->SeekRow( m_BookmarkOfFirstItemInCache, -m_nBlockSize, &lJumped ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_SEEKROW_FAILED; return &msc_ErrorEntry_SeekRowFailed; }
lJumped *= -1; // We have to change the sign on this number ( which will be negative )
ASSERT( SUCCEEDED( hr ) );
if( 0 == lJumped ) { // We are at the beginning of the list
m_IndexOfLastItemInCache -= m_IndexOfFirstItemInCache; m_IndexOfFirstItemInCache = 0; } else { // Free the beginningBookmark
hr = m_pContentsTable->FreeBookmark( m_BookmarkOfFirstItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_FREEBOOKMARK_FAILED; return &msc_ErrorEntry_FreeBookmarkFailed; }
// Set the beginningBookmark
hr = m_pContentsTable->CreateBookmark( &m_BookmarkOfFirstItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_CREATEBOOKMARK_FAILED; return &msc_ErrorEntry_CreateBookmarkFailed; } }
// QueryRow for lJumped items
lst< CGalEntry* >::iterator IInsertPos = m_EntryCache.begin();
// Get a block of rows
LPSRowSet pRow = NULL; hr = m_pContentsTable->QueryRows( lJumped, 0, &pRow );
if( FAILED( hr ) ) { m_hrGALError = GAL_E_QUERYROWS_FAILED; return &msc_ErrorEntry_QueryRowsFailed; }
// For each item in the block
for( ULONG i = 0; i < pRow->cRows; i++ ) {
CGalEntry* pEntry;
if( FAILED( _MakeGalEntry( pRow->aRow[ i ], &pEntry ) ) ) { lpfnFreeProws( pRow ); return &msc_ErrorEntry_NoInstanceKeyFound; }
// if the current item is equal to the first item in our list, we are done
--m_IndexOfFirstItemInCache; m_EntryCache.insert( IInsertPos, pEntry ); }
VERIFYCACHE
lpfnFreeProws( pRow );
if( FAILED( _KillExcessItemsFromBackOfCache() ) ) { // THis ist the only thing that can fail in _KillExcessItemsFromBackOfCache
return &msc_ErrorEntry_FreeBookmarkFailed; }
ASSERT( ( m_IndexOfLastItemInCache - m_IndexOfFirstItemInCache ) == static_cast< int >( m_EntryCache.size() - 1 ) ); // return the item corresponding to the index
return _GetItemFromCache( index ); }
HRESULT CGAL::_KillExcessItemsFromBackOfCache( void ) {
// if the cache size is greater than m_MaxCacheSize
if( m_EntryCache.size() > static_cast< size_t >( m_MaxCacheSize ) ) { // kill as many as we need to from the front of the list, fixing m_IndexOfFirstItemInCache
int NumItemsToKill = ( m_EntryCache.size() - m_MaxCacheSize ); while( NumItemsToKill-- ) { delete m_EntryCache.back(); m_EntryCache.erase( --( m_EntryCache.end() ) ); --m_IndexOfLastItemInCache; }
// Free the beginning bookmark
if( m_bEndBookmarkIsValid ) { // flag the front bookmark as invalid
m_bEndBookmarkIsValid = false; HRESULT hr = m_pContentsTable->FreeBookmark( m_BookmarkOfItemAfterLastItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_FREEBOOKMARK_FAILED; return m_hrGALError; } } } return S_OK; }
// In certain circumstances _CreateBeginningBookmark will return TRUE to indicate that the listView needs to be updated...
bool CGAL::_CreateBeginningBookmark( void ) {
HRESULT hr; bool bRet = false;
if( FAILED( hr = _SetCursorTo( *m_EntryCache.front() ) ) ) { if( MAPI_E_NOT_FOUND == hr ) { // The item is not in the table anymore. We have to
_LongJumpTo( m_IndexOfFirstItemInCache ); return true; } else { m_hrGALError = GAL_E_FINDROW_FAILED; return false; } }
hr = m_pContentsTable->CreateBookmark( &m_BookmarkOfFirstItemInCache ); m_bBeginningBookmarkIsValid = true; if( FAILED( hr ) ) { m_hrGALError = GAL_E_CREATEBOOKMARK_FAILED; return false; }
return false; }
// ruturn true if the item at IEntry is the item requested at index
bool CGAL::_CreateEndBookmark( int index, lst< CGalEntry* >::iterator& IEntry ) {
HRESULT hr; bool bRet = false; IEntry = m_EntryCache.end();
hr = _SetCursorTo( *m_EntryCache.back() ); if( FAILED( hr ) ) {
} if( FAILED( hr ) ) { if( MAPI_E_NOT_FOUND == hr ) { // This means that the listView needs to be update
ListView_RedrawItems( GetHwnd(), 0, m_nRows ); IEntry = m_EntryCache.end(); return true; } else { m_hrGALError = GAL_E_FINDROW_FAILED; return false; } }
// Get a block of entries
LPSRowSet pRow = NULL;
// Get a bunch of rows
hr = m_pContentsTable->QueryRows( m_nBlockSize, 0, &pRow );
if( FAILED( hr ) ) { m_hrGALError = GAL_E_QUERYROWS_FAILED; return false; } // If no entries are returned, this means that we have hit the end of the list
if( 0 == ( pRow->cRows ) ) { hr = m_pContentsTable->CreateBookmark( &m_BookmarkOfItemAfterLastItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_CREATEBOOKMARK_FAILED; return true; }
m_bEndBookmarkIsValid = true;
IEntry = --( m_EntryCache.end() ); return true; }
// Verify that the first entry is the last item in our list
ASSERT( 0 == memcmp( pRow->aRow[ 0 ].lpProps[ INSTANCEKEY_PROP_INDEX ].Value.bin.lpb, m_EntryCache.back()->GetInstanceKey().lpb, pRow->aRow[ 0 ].lpProps[ INSTANCEKEY_PROP_INDEX ].Value.bin.cb ) ); // for each entry returned
for( ULONG i = 1; i < pRow->cRows; i++ ) {
CGalEntry* pEntry; if( FAILED( _MakeGalEntry( pRow->aRow[ i ], &pEntry ) ) ) { lpfnFreeProws( pRow ); return false; }
// push it to the back of the entry list and increment m_IndexOfLastItemInCache
m_EntryCache.push_back( pEntry );
m_IndexOfLastItemInCache++; if( m_IndexOfLastItemInCache == index ) { bRet = true; IEntry = --( m_EntryCache.end() ); } }
lpfnFreeProws( pRow );
if( FAILED( _KillExcessItemsFromFrontOfCache() ) ) { // This is the only thang that can fail in _KillExcessItemsFromFrontOfCache
return false; }
ASSERT( ( m_IndexOfLastItemInCache - m_IndexOfFirstItemInCache ) == static_cast< int >( m_EntryCache.size() - 1 ) );
// Create a bookmark and store it in m_BookmarkOfItemAfterLastItemInCache
hr = m_pContentsTable->CreateBookmark( &m_BookmarkOfItemAfterLastItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_CREATEBOOKMARK_FAILED; return true; }
m_bEndBookmarkIsValid = true;
return bRet; }
// If the user is scrolling forwards and the ListView requests an item that is a little bit beyond the
// end of the cache, we have to get some more entries...
CGAL::CGalEntry* CGAL::_GetEntriesAtEndOfList( int index ) { lst< CGalEntry* >::iterator IRet; HRESULT hr;
// if m_bEndBookmarkIsValid
if( m_bEndBookmarkIsValid ) { // SeekRow to m_BookmarkOfItemAfterLastItemInCache
hr = m_pContentsTable->SeekRow( m_BookmarkOfItemAfterLastItemInCache, 0, NULL ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_SEEKROW_FAILED; return &msc_ErrorEntry_SeekRowFailed; } } else { // Set the end bookmark to the item after the last item in the cache
if( _CreateEndBookmark( index, IRet ) ) { if( IRet != m_EntryCache.end() ) { VERIFYCACHE return *IRet; } // this means that the end item is no longer in the GAL table
// we have to update the list view
_LongJumpTo( index ); ListView_RedrawItems( GetHwnd(), 0, m_nRows ); return &msc_ErrorEntry_FindRowFailed; } }
if( index > m_IndexOfLastItemInCache ) { // Get a block of entries
LPSRowSet pRow = NULL;
// Get a bunch of rows
hr = m_pContentsTable->QueryRows( m_nBlockSize, 0, &pRow ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_QUERYROWS_FAILED; return &msc_ErrorEntry_QueryRowsFailed; } // If no entries are returned, this means that we have hit the end of the list
if( 0 == ( pRow->cRows ) ) { return m_EntryCache.back(); }
// for each entry returned
for( ULONG i = 0; i < pRow->cRows; i++ ) {
CGalEntry* pEntry; if( FAILED( _MakeGalEntry( pRow->aRow[ i ], &pEntry ) ) ) { lpfnFreeProws( pRow ); return &msc_ErrorEntry_NoInstanceKeyFound; }
// push it to the back of the entry list and increment m_IndexOfLastItemInCache
m_EntryCache.push_back( pEntry );
m_IndexOfLastItemInCache++; // if m_IndexOfLastItemInCache == index, store the iterator for when we return the entry
if( index == m_IndexOfLastItemInCache ) { IRet = --( m_EntryCache.end() ); } } lpfnFreeProws( pRow );
// Free m_BookmarkOfItemAfterLastItemInCache
hr = m_pContentsTable->FreeBookmark( m_BookmarkOfItemAfterLastItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_FREEBOOKMARK_FAILED; return &msc_ErrorEntry_FreeBookmarkFailed; }
// Create a bookmark and store it in m_BookmarkOfItemAfterLastItemInCache
hr = m_pContentsTable->CreateBookmark( &m_BookmarkOfItemAfterLastItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_CREATEBOOKMARK_FAILED; return &msc_ErrorEntry_CreateBookmarkFailed; }
ASSERT( ( m_IndexOfLastItemInCache - m_IndexOfFirstItemInCache ) == static_cast< int >( m_EntryCache.size() - 1 ) ); }
if( FAILED( _KillExcessItemsFromFrontOfCache() ) ) { // This is the only thang that can fail in _KillExcessItemsFromFrontOfCache
return &msc_ErrorEntry_FreeBookmarkFailed; } VERIFYCACHE
// return the entry
return *IRet; }
// The only thing that can fail is freebookmark, in which case GAL_E_FREEBOOKMARK_FAILED is returned
HRESULT CGAL::_KillExcessItemsFromFrontOfCache( void ) {
// if the cache size is greater than m_MaxCacheSize
if( m_EntryCache.size() > static_cast< size_t >( m_MaxCacheSize ) ) {
// kill as many as we need to from the front of the list, fixing m_IndexOfFirstItemInCache
int NumItemsToKill = ( m_EntryCache.size() - m_MaxCacheSize ); while( NumItemsToKill-- ) { delete m_EntryCache.front(); m_EntryCache.erase( m_EntryCache.begin() ); ++m_IndexOfFirstItemInCache; }
// flag the front bookmark as invalid
m_bBeginningBookmarkIsValid = false; HRESULT hr = m_pContentsTable->FreeBookmark( m_BookmarkOfFirstItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_FREEBOOKMARK_FAILED; return GAL_E_FREEBOOKMARK_FAILED; }
}
return S_OK; }
// _GetItemInCache will return an entry from the cache
// the cache size should be set to a small enough number that
// the fact that we are using a linear search should not be a problem
// if we wanted to support a larger cache, another collection class other than a lst class
// would be used ( like a tree or a hash table )
CGAL::CGalEntry* CGAL::_GetItemFromCache( int index ) { ASSERT( ( m_IndexOfLastItemInCache - m_IndexOfFirstItemInCache ) == static_cast< int >( m_EntryCache.size() - 1 ) ); lst< CGalEntry* >::iterator I = m_EntryCache.begin(); int i = m_IndexOfFirstItemInCache; while( i != index ) { ASSERT( I != m_EntryCache.end() ); ++i, ++I; } return *I; }
// There may be a registry key that stores the mapi property that the user should
// use to find the ils server and username of people that the user calls with the GAL....
// If this reg key exists, the MAPI property will be queried when the user presses the CALL button
// in the dialog....
DWORD CGAL::_GetExchangeAttribute( void ) {
RegEntry re( msc_szNMPolRegKey, HKEY_CURRENT_USER ); return re.GetNumber( msc_szNMExchangeAtrValue, NM_INVALID_MAPI_PROPERTY ); }
void CGAL::_ResetCache( void ) { HRESULT hr;
lst< CGalEntry* >::iterator I = m_EntryCache.begin(); while( I != m_EntryCache.end() ) { delete ( *I ); I++; } m_EntryCache.erase( m_EntryCache.begin(), m_EntryCache.end() ); m_IndexOfFirstItemInCache = INVALID_CACHE_INDEX; m_IndexOfLastItemInCache = INVALID_CACHE_INDEX - 1; if( m_bBeginningBookmarkIsValid ) { hr = m_pContentsTable->FreeBookmark( m_BookmarkOfFirstItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_FREEBOOKMARK_FAILED; return; } } if( m_bEndBookmarkIsValid ) { hr = m_pContentsTable->FreeBookmark( m_BookmarkOfItemAfterLastItemInCache ); if( FAILED( hr ) ) { m_hrGALError = GAL_E_FREEBOOKMARK_FAILED; return; } } }
// Create a GAL Entry from a SRow structure returned by QueryRows
// The username and EMail name may be absent, this is not an error
// if the INSTANCE_KEY is missing, that would constitute an error
HRESULT CGAL::_MakeGalEntry( SRow& rRow, CGalEntry** ppEntry ) {
*ppEntry = NULL;
LPSPropValue lpProps = rRow.lpProps;
LPCTSTR szName; if( LOWORD( lpProps[ NAME_PROP_INDEX ].ulPropTag ) != PT_ERROR ) {
#ifdef UNICODE
szName = lpProps[ NAME_PROP_INDEX ].Value.lpszW; #else
szName = lpProps[ NAME_PROP_INDEX ].Value.lpszA; #endif // UNICODE
} else { szName = msc_szNoDisplayName; }
LPCTSTR szEMail; if( LOWORD( lpProps[ ACCOUNT_PROP_INDEX ].ulPropTag ) != PT_ERROR ) {
#ifdef UNICODE
szEMail = lpProps[ ACCOUNT_PROP_INDEX ].Value.lpszW; #else
szEMail = lpProps[ ACCOUNT_PROP_INDEX ].Value.lpszA; #endif // UNICODE
} else { szEMail = msc_szNoEMailName; } // Get the instance key
if( LOWORD( lpProps[ INSTANCEKEY_PROP_INDEX ].ulPropTag ) == PT_ERROR ) { m_hrGALError = GAL_E_NOINSTANCEKEY; return m_hrGALError; } ASSERT( PR_INSTANCE_KEY == lpProps[ INSTANCEKEY_PROP_INDEX ].ulPropTag ); SBinary& rInstanceKey = lpProps[ INSTANCEKEY_PROP_INDEX ].Value.bin;
// Get the entryid
if( LOWORD( lpProps[ ENTRYID_PROP_INDEX ].ulPropTag ) == PT_ERROR ) { m_hrGALError = GAL_E_NOENTRYID; return m_hrGALError; } ASSERT( PR_ENTRYID == lpProps[ ENTRYID_PROP_INDEX ].ulPropTag ); SBinary& rEntryID = lpProps[ ENTRYID_PROP_INDEX ].Value.bin; // Get the display Type
ULONG ulDisplayType = DT_MAILUSER; if( LOWORD( lpProps[ DISPLAY_TYPE_INDEX ].ulPropTag ) != PT_ERROR ) { ulDisplayType = lpProps[ DISPLAY_TYPE_INDEX ].Value.ul; }
// Get the business telephone number
LPCTSTR szBusinessTelephoneNum; if( LOWORD( lpProps[ BUSINESS_PHONE_NUM_PROP_INDEX ].ulPropTag ) != PT_ERROR ) {
#ifdef UNICODE
szBusinessTelephoneNum = lpProps[ BUSINESS_PHONE_NUM_PROP_INDEX ].Value.lpszW; #else
szBusinessTelephoneNum = lpProps[ BUSINESS_PHONE_NUM_PROP_INDEX ].Value.lpszA; #endif // UNICODE
} else { szBusinessTelephoneNum = msc_szNoBusinessTelephoneNum; }
*ppEntry = new CGalEntry( szName, szEMail, rInstanceKey, rEntryID, ulDisplayType, szBusinessTelephoneNum );
return S_OK; }
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
CGAL::CGalEntry::CGalEntry( void ) : m_szName( NULL ), m_szEMail( NULL ), m_ulDisplayType( DT_MAILUSER ), m_szBusinessTelephoneNum(NULL) { m_EntryID.cb = 0; m_EntryID.lpb = NULL; m_InstanceKey.cb = 0; m_InstanceKey.lpb = NULL; }
CGAL::CGalEntry::CGalEntry( const CGalEntry& r ) : m_szName( NULL ), m_szEMail( NULL ), m_ulDisplayType( DT_MAILUSER ), m_szBusinessTelephoneNum(NULL) { m_EntryID.cb = 0; m_EntryID.lpb = NULL; m_InstanceKey.cb = 0; m_InstanceKey.lpb = NULL; *this = r; }
CGAL::CGalEntry::CGalEntry( LPCTSTR szName, LPCTSTR szEMail, SBinary& rInstanceKey, SBinary& rEntryID, ULONG ulDisplayType, LPCTSTR szBusinessTelephoneNum ) : m_ulDisplayType( ulDisplayType ) { m_EntryID.cb = rEntryID.cb; m_InstanceKey.cb = rInstanceKey.cb;
if( m_EntryID.cb ) { m_EntryID.lpb = new BYTE[ m_EntryID.cb ]; memcpy( m_EntryID.lpb, rEntryID.lpb, m_EntryID.cb ); }
if( m_InstanceKey.cb ) { m_InstanceKey.lpb = new BYTE[ m_InstanceKey.cb ]; memcpy( m_InstanceKey.lpb, rInstanceKey.lpb, m_InstanceKey.cb ); }
m_szName = PszAlloc( szName ); m_szEMail = PszAlloc( szEMail ); m_szBusinessTelephoneNum = PszAlloc( szBusinessTelephoneNum ); }
CGAL::CGalEntry::CGalEntry( LPCTSTR szName, LPCTSTR szEMail ) : m_ulDisplayType( DT_MAILUSER ), m_szBusinessTelephoneNum(NULL) { m_EntryID.cb = 0; m_EntryID.lpb = NULL; m_InstanceKey.cb = 0; m_InstanceKey.lpb = NULL;
m_szName = PszAlloc( szName ); m_szEMail = PszAlloc( szEMail ); }
CGAL::CGalEntry::~CGalEntry( void ) { delete [] m_szName; delete [] m_szEMail; delete [] m_EntryID.lpb; delete [] m_InstanceKey.lpb; delete [] m_szBusinessTelephoneNum; }
CGAL::CGalEntry& CGAL::CGalEntry::operator=( const CGalEntry& r ) { if( this != &r ) { m_ulDisplayType = r.m_ulDisplayType;
delete [] m_EntryID.lpb; m_EntryID.lpb = NULL; delete [] m_InstanceKey.lpb; m_InstanceKey.lpb = NULL;
delete [] m_szName; delete [] m_szEMail; delete [] m_szBusinessTelephoneNum;
m_szName = NULL; m_szEMail = NULL; m_szBusinessTelephoneNum = NULL;
m_EntryID.cb = r.m_EntryID.cb; if( m_EntryID.cb ) { m_EntryID.lpb = new BYTE[ m_EntryID.cb ]; memcpy( m_EntryID.lpb, r.m_EntryID.lpb, m_EntryID.cb ); }
m_InstanceKey.cb = r.m_InstanceKey.cb; if( m_InstanceKey.cb ) { m_InstanceKey.lpb = new BYTE[ m_InstanceKey.cb ]; memcpy( m_InstanceKey.lpb, r.m_InstanceKey.lpb, m_InstanceKey.cb ); }
m_szName = PszAlloc( r.m_szName ); m_szEMail = PszAlloc( r.m_szEMail ); m_szBusinessTelephoneNum = PszAlloc( r.m_szBusinessTelephoneNum ); } return *this; }
bool CGAL::CGalEntry::operator==( const CGalEntry& r ) const { return ( ( m_InstanceKey.cb == r.m_InstanceKey.cb ) && ( 0 == memcmp( &m_InstanceKey.cb, &r.m_InstanceKey.cb, m_InstanceKey.cb ) ) ); }
bool CGAL::CGalEntry::operator>=( LPCTSTR sz ) const { return ( 0 <= lstrcmpi( m_szName, sz ) ); }
bool CGAL::CGalEntry::operator<( LPCTSTR sz ) const { return ( 0 > lstrcmpi( m_szName, sz ) ); }
bool CGAL::CGalEntry::operator<=( LPCTSTR sz ) const { return ( 0 >= lstrcmpi( m_szName, sz ) ); }
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Testin functions... These are pretty streight-forward....
#if TESTING_CGAL
void CGAL::_VerifyCache( void ) { #if 0
if( !IS_ZONE_ENABLED( ghZoneApi, ZONE_GALVERIFICATION_FLAG ) ) { return; } HRESULT hr; hr = _SetCursorTo( *m_EntryCache.front() );
lst< CGalEntry* >::iterator I = m_EntryCache.begin(); while( m_EntryCache.end() != I ) { LPSRowSet pRow; hr = m_pContentsTable->QueryRows ( 50, 0, &pRow ); ASSERT( SUCCEEDED( hr ) ); for( ULONG i = 0; i < pRow->cRows; i++ ) { CGalEntry* pEntry; _MakeGalEntry( pRow->aRow[ i ], &pEntry ); if( ( **I ) != ( *pEntry ) ) { ULONG Count; hr = m_pContentsTable->GetRowCount( 0, &Count ); ASSERT( SUCCEEDED( hr ) ); ASSERT( 0 ); lpfnFreeProws( pRow ); delete pEntry; return; } delete pEntry; I++; if( m_EntryCache.end() == I ) { break; }
} lpfnFreeProws( pRow ); } #endif
}
char* _MakeRandomString( void ) { static char sz[ 200 ]; int len = ( rand() % 6 ) + 1; sz[ len ] = '\0'; for( int i = len - 1; len >= 0; len-- ) { sz[ len ] = ( rand() % 26 ) + 'a'; }
return sz; }
void CGAL::_Test( void ) { int e = 7557; _GetEntry( e ); for( int o = 0; o < 10; o++ ) { _GetEntry( e - o ); } for( int i = 0; i < 500; i++ ) { int nEntry = rand() % ( m_nRows - 1 ); _GetEntry( nEntry ); if( rand() % 2 ) { // Slide for a while
int j, NewIndex; int nSlide = rand() % 100; if( rand() % 2 ) { // Slide Up for a while
for( j = 0; j < nSlide; j++ ) { NewIndex = j + nEntry; if( ( NewIndex >= 0 ) && ( NewIndex < static_cast< int >( m_nRows ) ) ) { _GetEntry( NewIndex ); } }
} else { // Slide Down for a while
for( j = 0; j < nSlide; j++ ) { NewIndex = nEntry - j; if( ( NewIndex >= 0 ) && ( NewIndex < static_cast< int >( m_nRows ) ) ) { _GetEntry( NewIndex ); } } } } } TRACE_OUT(( "The first test is successful!" ));
_ResetCache(); for( i = 0; i < 500; i++ ) { int nEntry = OnListFindItem( _MakeRandomString() ); if( rand() % 2 ) { // Slide for a while
int j, NewIndex; int nSlide = rand() % 100; if( rand() % 2 ) { // Slide Up for a while
for( j = 0; j < nSlide; j++ ) { NewIndex = j + nEntry; if( ( NewIndex >= 0 ) && ( NewIndex < static_cast< int >( m_nRows ) ) ) { _GetEntry( NewIndex ); } }
} else { // Slide Down for a while
for( j = 0; j < nSlide; j++ ) { NewIndex = nEntry - j; if( ( NewIndex >= 0 ) && ( NewIndex < static_cast< int >( m_nRows ) ) ) { _GetEntry( NewIndex ); }
} } }
}
TRACE_OUT(( "The second test is successful!" ));
}
#endif // #if TESTING_CGAL
|