ABCTBL1.C
/*********************************************************************** 
 * 
 *  ABCTBL1.C 
 * 
 *  Contents Table 
 * 
 *  The contents table associated with this provider.  It's file based. 
 *  The format of the .SAB file is in ABP.H. 
 * 
 *  This implementation of IMAPITable is retrieved by calling the 
 *  GetContentsTable() method of the sample ABP's single directory (see 
 *  abcont.c). 
 * 
 *  There are a few interesting features in this implementation.  First 
 *  it's implemented on a flat file (.SAB).  Second, it actually supports 
 *  an implementation of ANR (Ambiguous Name Resolution).  And lastly, it 
 *  supports FindRow (limited to prefix searching on display names). 
 * 
 *  This is the minimum set of restrictions that your provider MUST support. 
 *  It MUST have an ANR implementation and support prefix (downward) searching 
 *  on whatever your table is sorted on (in this case PR_DISPLAY_NAME). 
 * 
 *  The browsing of a flat file is done a record at a time.  It's never 
 *  completely read into memory.  It only reads records from the file when 
 *  it absolutely has to (like in QueryRows).  The advantage to this is 
 *  a low memory footprint and the ability to browse rather large files 
 *  with decent performance. 
 */ 
/* 
 *  ANR is also supported in this implementation.  In the code will often 
 *  be 'if' statements making two different code paths between the restricted 
 *  and unrestricted version.  The restrictions rely on a couple of 
 *  bit arrays.  Each bit corresponds (in order) to the records in the .SAB 
 *  file.  One bit array, rgChecked, says whether or not a record in the 
 *  .SAB file has actually been compared to the restriction.  It's initially 
 *  set to all 0s - which means that none of the records have been checked. 
 *  The second bit array, rgMatched, says whether or not the particular 
 *  record matches the current restriction.  This array is initialized to all 
 *  1s - which says that all the records in the .SAB file match the current 
 *  restriction.  The reason for this is for the fraction returned in 
 *  QueryPosition--By assuming that all the rows match and only updating 
 *  the array when something doesn't match, the fraction returned in 
 *  QueryPosition will get larger and has the effect of making the thumb-bar 
 *  on a listbox implemented on top of this table to scroll down as you go 
 *  down the list. 
 * 
 *  As a row is about to be read it is checked to see if it's been compared 
 *  to the current restriction.  If it has then to determine whether or not 
 *  to actually read the record and build the row we just look at the matched 
 *  bit array.  If it doesn't match we go on to the next record and check 
 *  again.  If it does match we read the record and build the row. 
 * 
 *  Only if it hasn't been checked do we actually go and read the row and 
 *  compare it to the current restriction, setting the rgChecked and 
 *  rgMatched bits accordingly. 
 * 
 *  In this implementation the ANR comparison rules are as follows: 
 *  (See FNameMatch for the actual code to do this) 
 * 
 *    A 'word' is defined as any substring separated by separators. 
 *    The separators are defined as ',', ' ', and '-'. 
 *    A restriction is said to match a display name iff all the words 
 *    in the restriction are all prefixes of words in the display name. 
 * 
 *    For example, a restriction of "b d" will match: 
 *     "Barney Donovan" 
 *     "Donovan, Barney" 
 *     "Danielle Blumenthal" 
 *    But will not match: 
 *     "Darby Opal" 
 *     "Ben Masters" 
 * 
 *  Other providers can do whatever matching they want with the ANR 
 *  restriction.  For example, soundex or additional field comparisons (like 
 *  email addresses).  A good (and fast) implementation will differentiate 
 *  your provider from others in a positive way. 
 * 
 *  FindRow's implementation effectively handles prefix searching on display 
 *  names (PR_DISPLAY_NAME).  It does this by binary searching the .SAB file. 
 *  The only tricky part is knowing when to stop.  Basically you want to stop 
 *  on the first name in the list that matches the restriction.  It's easy to 
 *  do linearly, but a little more trick with a binary search.  This 
 *  implementation is a reasonable example. 
 * 
 * 
 *  This table has only the following columns: 
 */ 
 
#include "abp.h" 
#include "abctbl.h" 
#include "sampabp.rh" 
 
const SizedSPropTagArray(cvalvtMax, tagaivtabcColSet) = 
{ 
    cvalvtMax, 
    { 
        PR_DISPLAY_NAME_A, 
        PR_ENTRYID, 
        PR_ADDRTYPE_A, 
        PR_EMAIL_ADDRESS_A, 
        PR_OBJECT_TYPE, 
        PR_DISPLAY_TYPE, 
        PR_INSTANCE_KEY 
    } 
}; 
const LPSPropTagArray ptagaivtabcColSet = (LPSPropTagArray) &tagaivtabcColSet; 
 
/* 
 * 
 *  The following routines are implemented in the files abctbl2.c and abctbl3.c: 
 * 
 * 
 *      IVTABC_Release 
 *      IVTABC_GetStatus 
 *      IVTABC_SetColumns 
 *      IVTABC_QueryColumns 
 *      IVTABC_GetRowCount 
 *      IVTABC_SeekRow 
 *      IVTABC_SeekRowApprox 
 *      IVTABC_QueryPosition 
 *      IVTABC_FindRow 
 *      IVTABC_Restrict 
 *      IVTABC_CreateBookmark 
 *      IVTABC_FreeBookmark 
 *      IVTABC_SortTable 
 *      IVTABC_QuerySortOrder 
 *      IVTABC_QueryRows 
 *      IVTABC_Abort 
 *      IVTABC_ExpandRow 
 *      IVTABC_CollapseRow 
 *      IVTABC_WaitForCompletion 
 *      IVTABC_GetCollapseState 
 *      IVTABC_SetCollapseState 
 * 
 * 
 *  This file (abctbl1.c) has all the utility functions used throughout this 
 *  objects methods.  They are the following: 
 * 
 *      HrNewIVTAbc() 
 *      HrValidateEntry() 
 *      FChecked() 
 *      FMatched() 
 *      FNameMatch() 
 *      HrOpenFile() 
 *      fIVTAbcIdleRoutine() 
 *      FreeANRBitmaps() 
 * 
 * 
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved. 
 * 
 ***********************************************************************/ 
 
/* 
 *  vtbl filled in here. 
 */ 
 
const IVTABC_Vtbl vtblIVTABC = 
{ 
 
    IVTABC_QueryInterface, 
    (IVTABC_AddRef_METHOD *)        ROOT_AddRef, 
    IVTABC_Release, 
    (IVTABC_GetLastError_METHOD *)  ROOT_GetLastError, 
    IVTABC_Advise, 
    IVTABC_Unadvise, 
    IVTABC_GetStatus, 
    IVTABC_SetColumns, 
    IVTABC_QueryColumns, 
    IVTABC_GetRowCount, 
    IVTABC_SeekRow, 
    IVTABC_SeekRowApprox, 
    IVTABC_QueryPosition, 
    IVTABC_FindRow, 
    IVTABC_Restrict, 
    IVTABC_CreateBookmark, 
    IVTABC_FreeBookmark, 
    IVTABC_SortTable, 
    IVTABC_QuerySortOrder, 
    IVTABC_QueryRows, 
    IVTABC_Abort, 
    IVTABC_ExpandRow, 
    IVTABC_CollapseRow, 
    IVTABC_WaitForCompletion, 
    IVTABC_GetCollapseState, 
    IVTABC_SetCollapseState 
}; 
 
 
/* Idle function */ 
FNIDLE fIVTAbcIdleRoutine; 
 
#define     IVTABC_IDLE_INTERVAL        300L 
#define     IVTABC_IDLE_PRIORITY        -2 
#define     IVTABC_IDLE_FLAGS           FIROINTERVAL 
 
/************************************************************************* 
 * 
 -  NewIVTAbc 
 - 
 *  Creates a new IMAPITable on the contents of a .SAB file. 
 * 
 * 
 */ 
HRESULT  
HrNewIVTAbc(LPMAPITABLE *       lppIVTAbc, 
            LPABLOGON           lpABLogon, 
            LPABCONT            lpABC, 
            HINSTANCE           hLibrary, 
            LPALLOCATEBUFFER    lpAllocBuff, 
            LPALLOCATEMORE      lpAllocMore, 
            LPFREEBUFFER        lpFreeBuff, 
            LPMALLOC            lpMalloc ) 
{ 
    LPIVTABC lpIVTAbc = NULL; 
    SCODE scode; 
    HRESULT hResult = hrSuccess; 
    ULONG ulBK, ulSize, ulSizeHigh; 
 
    /* 
     *  Allocate space for the IVTABC structure 
     */ 
 
    scode = lpAllocBuff(sizeof(IVTABC), (LPVOID *) &lpIVTAbc); 
    if (FAILED(scode)) 
    { 
        hResult = ResultFromScode(scode); 
        goto err; 
    } 
 
    lpIVTAbc->lpVtbl = &vtblIVTABC; 
    lpIVTAbc->lcInit = 1; 
    lpIVTAbc->hResult = hrSuccess; 
    lpIVTAbc->idsLastError = 0; 
 
    lpIVTAbc->hLibrary = hLibrary; 
    lpIVTAbc->lpAllocBuff = lpAllocBuff; 
    lpIVTAbc->lpAllocMore = lpAllocMore; 
    lpIVTAbc->lpFreeBuff = lpFreeBuff; 
    lpIVTAbc->lpMalloc = lpMalloc; 
    lpIVTAbc->lpABLogon = lpABLogon; 
 
    lpIVTAbc->lpszFileName = NULL; 
    hResult = HrLpszGetCurrentFileName(lpABLogon, &(lpIVTAbc->lpszFileName)); 
    if (HR_FAILED(hResult)) 
    { 
        goto err; 
    } 
 
    lpIVTAbc->lpPTAColSet = (LPSPropTagArray) &tagaivtabcColSet; 
 
    /* 
     *  Open the .SAB file associated with this logged in session. 
     *  Currently it's opened with FILE_SHARED_READ.  This has an 
     *  unpleasant side-effect of not being able to modify the .SAB 
     *  file while this table is active. 
     * 
     *  It should be OF_SHARE_DENY_NONE.  I don't have the code to 
     *  handle this in this version. 
     */ 
    if ((lpIVTAbc->hFile = CreateFile( 
                lpIVTAbc->lpszFileName, 
                GENERIC_READ, 
                FILE_SHARE_READ|FILE_SHARE_WRITE, 
                NULL, 
                OPEN_EXISTING, 
                FILE_ATTRIBUTE_NORMAL, 
                NULL)) == INVALID_HANDLE_VALUE) 
    { 
        /* 
         *  The file didn't open... 
         */ 
 
        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE); 
        SetErrorIDS(lpABC, hResult, IDS_CANT_OPEN_SAB); 
 
        goto err; 
    } 
 
    /* 
     *  Get the time and date stamp 
     * 
     */ 
    (void)GetFileTime(lpIVTAbc->hFile, NULL, NULL, &(lpIVTAbc->filetime)); 
 
    /*  Get the size of the file */ 
    if ((ulSize = GetFileSize(lpIVTAbc->hFile, &ulSizeHigh)) == 0xFFFFFFFF) 
    { 
        /* 
         *  MAYBE I have an error 
         */ 
        if (GetLastError() != NO_ERROR) 
        { 
            hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE); 
            SetErrorIDS(lpABC, hResult, IDS_SAB_FILE_ATTRIB); 
            goto err; 
        } 
 
        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE); 
        SetErrorIDS(lpABC, hResult, IDS_SAB_TOO_LARGE); 
        goto err; 
    } 
 
    /* 
     *  Actual number of valid positions 
     */ 
    lpIVTAbc->ulMaxPos = (ulSize / sizeof(ABCREC)); 
 
    /* 
     *  Check to see if it's an exact multiple of sizeof(ABCREC) 
     */ 
    if (lpIVTAbc->ulMaxPos * sizeof(ABCREC) != ulSize) 
    { 
        /* 
         *  Nope. 
         */ 
 
        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE); 
        SetErrorIDS(lpABC, hResult, IDS_SAB_CORRUPT); 
        goto err; 
    } 
 
    lpIVTAbc->ulPosition = 0; 
 
    /*  Setup bookmarks  */ 
    for (ulBK = 0; ulBK < MAX_BOOKMARKS; lpIVTAbc->rglpABCBK[ulBK++] = NULL); 
 
    /* 
     *  Restriction stuff 
     */ 
 
    lpIVTAbc->rgChecked = NULL; 
    lpIVTAbc->rgMatched = NULL; 
    lpIVTAbc->lpszPartialName = NULL; 
 
    /* 
     *  Registered notification stuff 
     */ 
 
    lpIVTAbc->cAdvise = 0; 
    lpIVTAbc->parglpAdvise = NULL; 
    lpIVTAbc->ulConnectMic = (ULONG) lpIVTAbc & 0xffffff; 
    InitializeCriticalSection(&lpIVTAbc->cs); 
 
    /* 
     *  Register the idle function 
     */ 
 
    lpIVTAbc->ftg = FtgRegisterIdleRoutine( 
        fIVTAbcIdleRoutine, 
        lpIVTAbc, 
        IVTABC_IDLE_PRIORITY, 
        IVTABC_IDLE_INTERVAL, 
        IVTABC_IDLE_FLAGS); 
 
    InitializeCriticalSection(&lpIVTAbc->cs); 
 
    /*  We must AddRef the lpABLogon object since we will be using it 
     */ 
    lpABLogon->lpVtbl->AddRef(lpABLogon); 
 
    *lppIVTAbc = (LPVOID) lpIVTAbc; 
 
out: 
 
         
    DebugTraceResult(HrNewIVTAbc, hResult); 
    return hResult; 
 
err: 
    if (lpIVTAbc) 
    { 
        if (lpIVTAbc->hFile != INVALID_HANDLE_VALUE) 
        { 
            /* 
             *  Must've opened the file 
             */ 
            CloseHandle(lpIVTAbc->hFile); 
        } 
 
        /*  Free the current .SAB file name */ 
        lpFreeBuff(lpIVTAbc->lpszFileName); 
 
        /*  Free the mem */ 
        lpFreeBuff( lpIVTAbc ); 
    } 
 
    goto out; 
 
} 
 
/************************************************************************* 
 * 
 -  HrValidateEntry 
 - 
 * 
 *  Used in restricted lists 
 *  Checks to see if a given entry at a particular location matches 
 *  the active restriction.  It always modifies rgChecked, and may 
 *  modify rgMatched. 
 */ 
HRESULT  
HrValidateEntry(LPIVTABC lpIVTAbc, ULONG ulNewPos) 
{ 
    ABCREC abcrec; 
    ULONG cbRead; 
    HRESULT hResult; 
 
    /* 
     *  Open the file 
     */ 
    hResult = HrOpenFile(lpIVTAbc); 
    if (HR_FAILED(hResult)) 
    { 
        DebugTraceResult(HrValidateEntry, hResult); 
        return hResult; 
    } 
 
    /* 
     *  Set the file position to lNewPos 
     */ 
 
    (void) SetFilePointer(lpIVTAbc->hFile, ulNewPos * sizeof(ABCREC), 
                          NULL, FILE_BEGIN); 
 
    /* 
     *  Read in the record from the file 
     */ 
    if (!ReadFile(lpIVTAbc->hFile, (LPVOID) &abcrec, 
            sizeof(ABCREC), &cbRead, NULL)) 
    { 
        DebugTraceSc(HrValidateEntry, MAPI_E_DISK_ERROR); 
        return ResultFromScode(MAPI_E_DISK_ERROR); 
    } 
 
    /*  Second check  */ 
    if (cbRead != sizeof(ABCREC)) 
    { 
        DebugTraceSc(HrValidateEntry, MAPI_E_DISK_ERROR); 
        return ResultFromScode(MAPI_E_DISK_ERROR); 
    } 
 
    /* 
     *  Always set the Checked flag 
     */ 
    lpIVTAbc->rgChecked[ulNewPos / 8] |= (((unsigned char)0x80) >> (ulNewPos % 8)); 
 
    /* 
     *  See if the rgchDisplayName matches the restriction 
     */ 
    if (!FNameMatch(lpIVTAbc, abcrec.rgchDisplayName)) 
    { 
        /* 
         *  Apparently not.  Reset the Matched flag 
         */ 
 
        lpIVTAbc->ulRstrDenom--; 
 
        lpIVTAbc->rgMatched[ulNewPos / 8] &= ~(((unsigned char)0x80) >> (ulNewPos % 8)); 
    } 
 
    return hrSuccess; 
} 
 
/************************************************************************* 
 * 
 -  FChecked 
 - 
 * 
 *  Returns whether or not an entry has ever been checked 
 *  Just looks at the bit in the rgChecked array that corresponds with 
 *  lNewPos 
 * 
 */ 
BOOL 
FChecked(LPIVTABC lpIVTAbc, ULONG ulNewPos) 
{ 
    ULONG ulT = (ULONG) (lpIVTAbc->rgChecked[ulNewPos / 8] & (((unsigned char)0x80) >> (ulNewPos % 8))); 
 
    return (BOOL) !!ulT; 
} 
 
/************************************************************************* 
 * 
 -  FMatched 
 - 
 * 
 *  Returns whether or not an entry has been matched 
 *  Just checks the bit in the rgMatched array corresponding with 
 *  lNewPos 
 * 
 */ 
BOOL 
FMatched(LPIVTABC lpIVTAbc, ULONG ulNewPos) 
{ 
    ULONG ulT = (lpIVTAbc->rgMatched[ulNewPos / 8] & (((unsigned char)0x80) >> (ulNewPos % 8))); 
 
    return (BOOL) !!ulT; 
 
} 
 
/************************************************************************* 
 * 
 -  FNameMatch 
 - 
 *  Tries to match the rgchDisplayName with the partial name of the 
 *  restriction. 
 *  It tries to prefix match each partial name component (i.e. word) with 
 *  each rgchDisplayName name component (i.e. word).  Only if all of them 
 *  match (from the partial name) does this return TRUE. 
 */ 
BOOL 
FCharInString(LPSTR lpsz, CHAR ch); 
 
 
BOOL 
FNameMatch(LPIVTABC lpIVTAbc, LPSTR rgchDisplayName) 
{ 
    LPSTR szANRSep = ", -"; 
    LPSTR szANR = lpIVTAbc->lpszPartialName; 
    LPSTR pchEndSzANR = szANR + lstrlenA(szANR); 
    ULONG cchANRName; 
    ULONG cchFullNameName; 
    LPSTR szFullNameT; 
    LPSTR szT; 
 
    /*  If someone tries to match more than an iwMOMax-part name, the function 
     *  will return fFalse.  But then if someone is trying to match a name 
     *  with iwMOMax parts, chances are they weren't going to get it right 
     *  anyway.... 
     */ 
 
#define iwMOMax 50 
 
    WORD rgwMO[iwMOMax]; 
    int iwMOMac = 0; 
 
    while (TRUE) 
    { 
        /*  Find the end of the partial name we're pointing at  */ 
 
        szT = szANR; 
        while (!FCharInString(szANRSep, *szT)) 
            ++szT; 
        cchANRName = szT - szANR; 
 
        /*  Check if it matches any name in the full name  */ 
 
        szFullNameT = rgchDisplayName; 
        while (TRUE) 
        { 
            szT = szFullNameT; 
 
            /*  Find the length of the name in the full name  */ 
            /*  we're checking against.                       */ 
 
            while (!FCharInString(szANRSep, *szT)) 
                ++szT; 
            cchFullNameName = szT - szFullNameT; 
 
            if (cchANRName <= cchFullNameName && 
                CompareStringA( lcidUser, 
                                NORM_IGNORECASE, 
                                szANR, 
                                (int) cchANRName, 
                                szFullNameT, 
                                (int) cchANRName) == 2 ) 
            { 
                int iwMO; 
 
                for (iwMO = 0; iwMO < iwMOMac; iwMO++) 
                    if (rgwMO[iwMO] == (WORD) (szFullNameT - rgchDisplayName)) 
                        break; 
 
                /*  We found the partial name so check the next ANR part */ 
                if (iwMO == iwMOMac) 
                { 
                    if (iwMOMac == iwMOMax - 1) 
                    { 
                        /*  If some user wants to match an iwMOMax part 
                         *  name, chances are it wasn't going to match 
                         *  anyway... 
                         */ 
                        return FALSE; 
                    } 
                    rgwMO[iwMOMac++] = szFullNameT - rgchDisplayName; 
                    break; 
                } 
            } 
 
            /*  We didn't find the partial name this time around, so 
             *  try to check the next name in the full name. 
             */ 
 
            szFullNameT += cchFullNameName; 
 
            while (*szFullNameT && FCharInString(szANRSep, *szFullNameT)) 
                ++szFullNameT; 
 
            if (*szFullNameT == '\0') 
                return FALSE;   /*  We never found the partial name. */ 
        } 
 
        /* We found the partial name, so check the next ANR part */ 
 
        szANR += cchANRName; 
        while (*szANR && FCharInString(szANRSep, *szANR)) 
            ++szANR; 
 
        if (*szANR == '\0') 
            return TRUE;        /* No more ANR to check, so we found `em all  */ 
    } 
 
    /*  Not reached (we hope...)  */ 
    return FALSE; 
} 
 
/* 
 -  HrOpenFile 
 - 
 *  Opens the .SAB file associated with the table and 
 *  checks whether the .SAB file has changed. 
 *  If it has changed, the table bookmarks and ANR bitmaps 
 *  are updated and everyone on the advise list is notified. 
 */ 
HRESULT  
HrOpenFile(LPIVTABC lpIVTAbc) 
{ 
    HRESULT hResult = hrSuccess; 
    FILETIME filetime; 
    ULONG ulSize, ulSizeHigh, ulMaxPos; 
    LPSTR lpszFileName = NULL; 
 
    /* 
     *  If file is not open, open it 
     */ 
    if (lpIVTAbc->hFile == INVALID_HANDLE_VALUE) 
    { 
 
        if (!FEqualSABFiles(lpIVTAbc->lpABLogon, lpIVTAbc->lpszFileName)) 
        { 
            /* 
             *  Get the new file name 
             */ 
            hResult = HrLpszGetCurrentFileName(lpIVTAbc->lpABLogon, &lpszFileName); 
            if (HR_FAILED(hResult)) 
            { 
                goto err; 
            } 
 
            /* 
             *  Replace the old one with this 
             */ 
            lpIVTAbc->lpFreeBuff(lpIVTAbc->lpszFileName); 
            lpIVTAbc->lpszFileName = lpszFileName; 
 
            lpszFileName = NULL; 
        } 
 
         
        /* 
         *  File is not open so lets try to open it 
         */ 
        lpIVTAbc->hFile = CreateFile( 
            lpIVTAbc->lpszFileName, 
            GENERIC_READ, 
            FILE_SHARE_READ|FILE_SHARE_WRITE, 
            NULL, 
            OPEN_EXISTING, 
            FILE_ATTRIBUTE_NORMAL, 
            NULL); 
 
        if (lpIVTAbc->hFile == INVALID_HANDLE_VALUE) 
        { 
            /* 
             *  The file didn't open... 
             */ 
            hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE); 
            SetErrorIDS(lpIVTAbc, hResult, IDS_CANT_OPEN_SAB_FILE); 
            goto err; 
        } 
    } 
 
    /* 
     *  Get the time and date stamp 
     */ 
    if (!GetFileTime(lpIVTAbc->hFile, NULL, NULL, &filetime)) 
    { 
        if (GetLastError() != NO_ERROR) 
        { 
            hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE); 
            SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_FILE_ATTRIB); 
        } 
 
        goto err; 
    } 
 
    /*  Get the size of the file */ 
    if ((ulSize = GetFileSize(lpIVTAbc->hFile, &ulSizeHigh)) == 0xFFFFFFFF) 
    { 
        /* 
         *  MAYBE I have an error 
         */ 
        if (GetLastError() != NO_ERROR) 
        { 
            hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE); 
            SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_FILE_ATTRIB); 
            goto err; 
        } 
 
        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE); 
        SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_TOO_LARGE); 
        goto err; 
    } 
 
    /* 
     *  Actual number of valid positions 
     */ 
    ulMaxPos = (ulSize / sizeof(ABCREC)); 
 
    /* 
     *  Check to see if it's an exact multiple of sizeof(ABCREC) 
     */ 
    if (ulMaxPos * sizeof(ABCREC) != ulSize) 
    { 
        hResult = ResultFromScode(MAPI_E_UNABLE_TO_COMPLETE); 
        SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_CORRUPT); 
        goto err; 
    } 
 
    /* 
     *  if the file has changed set new position, reset bookmarks etc and 
     *  notify everybody one the advise list 
     */ 
    if (CompareFileTime(&filetime, &lpIVTAbc->filetime) || ulMaxPos != lpIVTAbc->ulMaxPos) 
    { 
        ULONG ulBK; 
        ABCREC abcrec; 
        ULONG cbT; 
        LPMAPIADVISESINK *ppadvise; 
        ULONG cAdvises; 
        NOTIFICATION notif; 
 
        /* save new max position and filetime */ 
        lpIVTAbc->filetime = filetime; 
        lpIVTAbc->ulMaxPos = ulMaxPos; 
 
        /* if current position is past the end of file set it to the end */ 
        if (lpIVTAbc->ulPosition > lpIVTAbc->ulMaxPos) 
            lpIVTAbc->ulPosition = lpIVTAbc->ulMaxPos; 
 
        if (ulMaxPos) 
        { 
            SetFilePointer(lpIVTAbc->hFile, (ulMaxPos - 1)*sizeof(ABCREC), NULL, FILE_BEGIN); 
 
            /*  Read in the record at that location  */ 
            if (!ReadFile(lpIVTAbc->hFile, (LPVOID) &abcrec, 
                    sizeof(ABCREC), &cbT, NULL)) 
            { 
                hResult = ResultFromScode(MAPI_E_DISK_ERROR); 
                SetErrorIDS(lpIVTAbc, hResult, IDS_SAB_NO_READ); 
 
                goto err; 
            } 
 
            /* if any of the bookmarks are past the end of file 
             * set the file time to current file time, the position to last 
             * record and record to last record 
             */ 
            for (ulBK = 0; ulBK < MAX_BOOKMARKS; ulBK++) 
                if (lpIVTAbc->rglpABCBK[ulBK] && 
                    lpIVTAbc->rglpABCBK[ulBK]->ulPosition > lpIVTAbc->ulMaxPos) 
                { 
                    lpIVTAbc->rglpABCBK[ulBK]->ulPosition = ulMaxPos - 1; 
                    lpIVTAbc->rglpABCBK[ulBK]->filetime = filetime; 
                    lpIVTAbc->rglpABCBK[ulBK]->abcrec = abcrec; 
                } 
 
            /* 
            *  Reallocate the checked&matched arrays 
            */ 
 
            cbT = (lpIVTAbc->ulMaxPos) / 8 + 1; /* Number of bytes in both arrays */ 
 
            /* Reallocate ANR bitmaps */ 
            if (lpIVTAbc->rgChecked) 
            { 
                lpIVTAbc->rgChecked = lpIVTAbc->lpMalloc->lpVtbl->Realloc( 
                    lpIVTAbc->lpMalloc, 
                    lpIVTAbc->rgChecked, 
                    cbT); 
            } 
 
            if (lpIVTAbc->rgMatched) 
            { 
                lpIVTAbc->rgMatched = lpIVTAbc->lpMalloc->lpVtbl->Realloc( 
                    lpIVTAbc->lpMalloc, 
                    lpIVTAbc->rgMatched, 
                    cbT); 
            } 
        } 
        else 
        { 
            /* if any of the bookmarks are past the end of file 
             * set the file time to current file time, the position to the 
             * beginning of the file. 
             */ 
            for (ulBK = 0; ulBK < MAX_BOOKMARKS; ulBK++) 
                if (lpIVTAbc->rglpABCBK[ulBK] && 
                    lpIVTAbc->rglpABCBK[ulBK]->ulPosition > lpIVTAbc->ulMaxPos) 
                { 
                    lpIVTAbc->rglpABCBK[ulBK]->ulPosition = 0; 
                    lpIVTAbc->rglpABCBK[ulBK]->filetime = filetime; 
                } 
 
            /* free the ANR bitmaps */ 
            FreeANRBitmaps(lpIVTAbc); 
        } 
 
        /* initialize the notification */ 
        ZeroMemory(¬if, sizeof(NOTIFICATION)); 
        notif.ulEventType = fnevTableModified; 
        notif.info.tab.ulTableEvent = TABLE_RELOAD; 
 
        /* notify everyone that the table has changed */ 
        for (ppadvise = lpIVTAbc->parglpAdvise, cAdvises = 0; 
            cAdvises < lpIVTAbc->cAdvise; 
            ++ppadvise, ++cAdvises) 
        { 
            Assert(*ppadvise); 
            if (ppadvise) 
                (void)(*ppadvise)->lpVtbl->OnNotify(*ppadvise, 1, ¬if); 
        } 
    } 
 
out: 
 
    DebugTraceResult(NewIVTAbc, hResult); 
    return hResult; 
err: 
    lpIVTAbc->lpFreeBuff(lpszFileName); 
 
    goto out; 
 
} 
 
/************************************************************************* 
 * 
 -  fIVTAbcIdleRoutine 
 - 
 *  This function called during idle time closes the .SAB file and notifies 
 *  everyone on the advise list if the file name has changed 
 * 
 */ 
 
BOOL STDAPICALLTYPE 
fIVTAbcIdleRoutine(LPVOID lpv) 
{ 
    LPIVTABC lpIVTAbc = (LPIVTABC) lpv; 
 
    Assert(lpv); 
 
    /* if file is open close it  */ 
    if (lpIVTAbc->hFile != INVALID_HANDLE_VALUE) 
    { 
        CloseHandle(lpIVTAbc->hFile); 
        lpIVTAbc->hFile = INVALID_HANDLE_VALUE; 
    } 
 
    /* has file name has changed? */ 
    if (!FEqualSABFiles(lpIVTAbc->lpABLogon, lpIVTAbc->lpszFileName)) 
    { 
        /* file name has changed so call HrOpenFile to reset bookmarks etc */ 
        if (!HR_FAILED(HrOpenFile(lpIVTAbc))) 
        { 
            /* close the file */ 
            CloseHandle(lpIVTAbc->hFile); 
            lpIVTAbc->hFile = INVALID_HANDLE_VALUE; 
        } 
    } 
    return TRUE; 
} 
 
/************************************************************************* 
 * 
 -  FreeANRBitmaps 
 - 
 *  Frees the two ANR bitmaps associated with this table 
 * 
 * 
 */ 
void  
FreeANRBitmaps(LPIVTABC lpIVTAbc) 
{ 
    if (lpIVTAbc->rgChecked) 
    { 
        lpIVTAbc->lpMalloc->lpVtbl->Free(lpIVTAbc->lpMalloc, lpIVTAbc->rgChecked); 
        lpIVTAbc->rgChecked = NULL; 
    } 
 
    if (lpIVTAbc->rgMatched) 
    { 
        lpIVTAbc->lpMalloc->lpVtbl->Free(lpIVTAbc->lpMalloc, lpIVTAbc->rgMatched); 
        lpIVTAbc->rgMatched = NULL; 
    } 
} 
 
 
 
/* 
 *  FCharInString 
 * 
 *  Finds a character in a string 
 */ 
BOOL 
FCharInString(LPSTR lpsz, CHAR ch) 
{ 
 
    while (*lpsz && *lpsz != ch) 
        lpsz++; 
 
    return (*lpsz == ch); 
}