mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2118 lines
63 KiB
2118 lines
63 KiB
// 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
|
|
|