// WWheel.cpp - HTML Help Word Wheel support // // Covers both KeywordLinks and AssociativeLinks // // // needed for those pesky pre-compiled headers #include "header.h" #include "wwheel.h" #include "animate.h" #include "strtable.h" #include "resource.h" #include "util.h" // memory leak checks AUTO_CLASS_COUNT_CHECK(CTitleMapEntry); AUTO_CLASS_COUNT_CHECK(CTitleMap); AUTO_CLASS_COUNT_CHECK(CTitleDatabase); AUTO_CLASS_COUNT_CHECK(CResultsEntry); AUTO_CLASS_COUNT_CHECK(CResults); AUTO_CLASS_COUNT_CHECK(CWordWheelEntry); AUTO_CLASS_COUNT_CHECK(CWordWheel); AUTO_CLASS_COUNT_CHECK(CWordWheelCompiler); // taken from "hhsyssrt.h" // {4662dab0-d393-11d0-9a56-00c04fb68b66} // HACKHACK: I simply changed the last value of CLSID_ITSysSort from 0xf7 to 0x66 DEFINE_GUID(CLSID_HHSysSort, 0x4662dab0, 0xd393, 0x11d0, 0x9a, 0x56, 0x00, 0xc0, 0x4f, 0xb6, 0x8b, 0x66); // old format #define IHHSK666_KEYTYPE_ANSI_SZ ((DWORD) 66630) // NULL-term. MBCS string + extra data #define IHHSK666_KEYTYPE_UNICODE_SZ ((DWORD) 66631) // NULL-term. Unicode string + extra data #if 0 // New format #define IHHSK100_KEYTYPE_ANSI_SZ ((DWORD) 10030) // NULL-term. MBCS string + extra data #define IHHSK100_KEYTYPE_UNICODE_SZ ((DWORD) 10031) // NULL-term. Unicode string + extra data #endif #define IHHSK100_KEYTYPE_ANSI_SZ ((DWORD) 30) // NULL-term. MBCS string + extra data #define IHHSK100_KEYTYPE_UNICODE_SZ ((DWORD) 31) // NULL-term. Unicode string + extra data #ifdef _DEBUG #undef THIS_FILE static const char THIS_FILE[] = __FILE__; #endif // Global Variables static const CHAR g_szKeywordLinks[] = "KeywordLinks"; static const CHAR g_szAssociativeLinks[] = "AssociativeLinks"; static const CHAR g_szTitleMap[] = "$HHTitleMap"; static const WCHAR g_wszKeywordLinks[] = L"KeywordLinks"; static const WCHAR g_wszAssociativeLinks[] = L"AssociativeLinks"; static const WCHAR g_wszTitleMap[] = L"$HHTitleMap"; static const WCHAR g_wszError[] = L"(ERROR)"; DWORD g_dwError = ((DWORD)-1); ///////////////////////////////////////////////////////////////////////////// // helpful functions static const CHAR g_szBusyFile[] = "HTMLHelpKeywordMergingBusy"; static HANDLE g_hFileBusy = NULL; #define BUSY_FILE_SIZE 32 BOOL IsBusy() { BOOL bBusy = FALSE; HANDLE hFileBusy = NULL; SetLastError(0); hFileBusy = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READONLY, 0, BUSY_FILE_SIZE, g_szBusyFile ); if( hFileBusy ) { if( GetLastError() == ERROR_ALREADY_EXISTS ) bBusy = TRUE; CloseHandle( hFileBusy ); } return bBusy; } void SetBusy( BOOL bBusy ) { if( bBusy ) { if( !g_hFileBusy ) g_hFileBusy = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READONLY, 0, BUSY_FILE_SIZE, g_szBusyFile ); } else { if( g_hFileBusy ) { CloseHandle( g_hFileBusy ); g_hFileBusy = NULL; } } } int FASTCALL CompareIds( const void* p1, const void* p2 ) { int iReturn; CTitleMapEntry* pEntry1= (CTitleMapEntry*) p1; CTitleMapEntry* pEntry2= (CTitleMapEntry*) p2; DWORD dwId1 = pEntry1->GetId(); DWORD dwId2 = pEntry2->GetId(); if( dwId1 < dwId2 ) iReturn = -1; else if ( dwId1 > dwId2 ) iReturn = 1; else iReturn = 0; return iReturn; } // check if a specified subfile exists in the specified title BOOL IsSubFile( PCSTR pszTitlePathname, PCSTR pszSubFile ) { BOOL bExists = FALSE; if( pszTitlePathname && pszTitlePathname[0] && pszSubFile && pszSubFile[0] ) { HRESULT hr = S_OK; CFileSystem* pFS = new CFileSystem; if( pFS && SUCCEEDED(hr = pFS->Init()) && SUCCEEDED(hr = pFS->Open( pszTitlePathname )) ) { CSubFileSystem* pSFS = new CSubFileSystem( pFS ); if( pSFS && SUCCEEDED(pSFS->OpenSub(pszSubFile)) ) { bExists = TRUE; delete pSFS; } delete pFS; } } return bExists; } ///////////////////////////////////////////////////////////////////////////// // class CTitleMap implementation BOOL CTitleMap::Initialize() { // bail out if we are already initialized if( m_bInit ) return TRUE; // allocate a MBCS version of our file system pathname CHAR* psz = NULL; if( m_pszDatabase && *m_pszDatabase ) { DWORD dwLen = (DWORD)strlen(m_pszDatabase) + 1; psz = new char[dwLen]; strcpy(psz,m_pszDatabase); } m_pszDatabase = psz; // set this bool first since we are going to call GetAt() // which will make a reentrant call if this is not set-- // this would be bad! m_bInit = TRUE; // if we have a database, read in the data if( m_pszDatabase ) { HRESULT hr = S_OK; // open the database and read in the subfile CFileSystem* pDatabase = new CFileSystem; if( SUCCEEDED(hr = pDatabase->Init()) && SUCCEEDED(hr = pDatabase->Open( GetDatabase() )) ) { CSubFileSystem* pTitleMap = new CSubFileSystem( pDatabase ); if( SUCCEEDED(hr = pTitleMap->OpenSub( g_szTitleMap ) ) ) { // format of TitleMap subfile is as follows: // // wCount (number of entries) // wShortNameLen, sShortName, FILETIME, LCID // ...line above repeated for each entry... // ULONG cbRead = 0; WORD wCount = 0; pTitleMap->ReadSub( (void*) &wCount, sizeof(wCount), &cbRead ); SetCount( (DWORD) wCount ); for( int iCount = 0; iCount < (int) wCount; iCount++ ) { WORD wLen = 0; char szShortName[256]; FILETIME FileTime; LCID lcid; pTitleMap->ReadSub( (void*) &wLen, sizeof(wLen), &cbRead ); pTitleMap->ReadSub( (void*) &szShortName, wLen, &cbRead ); szShortName[wLen] = 0; ASSERT(cbRead != 0) ; // See HH Bug 2807 --- Saved a NULL shortname. // This means that the CHM file associted with this // topic probably doesn't exist. Tell the owner of the collection. pTitleMap->ReadSub( (void*) &FileTime, sizeof(FileTime), &cbRead ); pTitleMap->ReadSub( (void*) &lcid, sizeof(lcid), &cbRead ); GetAt((DWORD)iCount)->SetId( iCount+1 ); GetAt((DWORD)iCount)->SetShortName( szShortName ); GetAt((DWORD)iCount)->SetFileTime( FileTime ); GetAt((DWORD)iCount)->SetLanguage( lcid ); } } delete pTitleMap; } delete pDatabase; } else m_bInit = FALSE; return m_bInit; } BOOL CTitleMap::Free() { if( m_pEntries ) { delete [] m_pEntries; m_pEntries = NULL; m_dwCount = HHWW_ERROR; } if( m_pszDatabase ) { delete [] (CHAR*) m_pszDatabase; m_pszDatabase = NULL; } m_bInit = FALSE; return TRUE; } ///////////////////////////////////////////////////////////////////////////// // class CTitleDatabase implementation (Shared Centaur object) void CTitleDatabase::_CTitleDatabase() { m_bInit = FALSE; m_pDatabase = NULL; m_pwszDatabase = NULL; m_pszDatabase = NULL; m_bCollection = FALSE; m_pTitleMap = NULL; m_pKeywordLinks = NULL; m_pAssociativeLinks = NULL; m_pCollection = NULL; m_pTitle = NULL; #ifdef CHIINDEX m_bAnimation = TRUE; // display animation #endif } CTitleDatabase::CTitleDatabase( CExCollection* pCollection ) { _CTitleDatabase(); m_pCollection = pCollection; } CTitleDatabase::CTitleDatabase( CExTitle* pTitle ) { _CTitleDatabase(); m_pTitle = pTitle; } CTitleDatabase::CTitleDatabase( const WCHAR* pwszDatabase ) { _CTitleDatabase(); m_pwszDatabase = pwszDatabase; } CTitleDatabase::CTitleDatabase( const CHAR* pszDatabase ) { _CTitleDatabase(); m_pszDatabase = pszDatabase; } CTitleDatabase::~CTitleDatabase() { Free(); } BOOL CTitleDatabase::Initialize(CHAR *pszFileName) { BOOL bReturn = FALSE; HRESULT hr = S_OK; // bail out if we are already initialized if( m_bInit ) return TRUE; // bail out if collection or title not specified if( !(m_pCollection || m_pTitle || m_pwszDatabase || m_pszDatabase ) ) return FALSE; // set the hourglass cursor CHourGlass HourGlass; // get the database name if( m_pCollection ) { m_pTitle = m_pCollection->GetFirstTitle(); m_bCollection = ( m_pCollection && (m_pCollection->GetRefedTitleCount() > 1) ); if( m_bCollection ) m_pszDatabase = m_pCollection->GetLocalStoragePathname(".chw"); else m_pszDatabase = m_pTitle->GetIndexFileName(); } else if( m_pTitle ) { m_pszDatabase = m_pTitle->GetIndexFileName(); } if (pszFileName) { char drive[_MAX_PATH], dir[_MAX_PATH]; splitpath(m_pszDatabase, drive, dir, NULL, NULL); if (drive[0] && drive[strlen(drive)-1] != '\\' && dir[0] != '\\') strcat(drive, "\\"); strcat(drive, dir); if (drive[strlen(drive)-1] != '\\' && pszFileName[0] != '\\') strcat(drive, "\\"); strcat(drive, pszFileName); strcpy(m_szFullPath, drive); m_pszDatabase = m_szFullPath; } // allocate UNICODE and MBCS versions of our file system pathname WCHAR* pwsz = NULL; CHAR* psz = NULL; if( m_pszDatabase && *m_pszDatabase ) { DWORD dwLen = (DWORD)strlen(m_pszDatabase) + 1; pwsz = new WCHAR[dwLen]; MultiByteToWideChar(CP_ACP, 0, m_pszDatabase, -1, pwsz, dwLen); psz = new char[dwLen]; strcpy(psz,m_pszDatabase); } else if( m_pwszDatabase && *m_pwszDatabase ) { DWORD dwLen = wcslen(m_pwszDatabase) + 1; pwsz = new WCHAR[dwLen]; wcscpy(pwsz,m_pwszDatabase); psz = new char[dwLen]; WideCharToMultiByte(CP_ACP, 0, m_pwszDatabase, -1, psz, dwLen, NULL, NULL); } m_pwszDatabase = pwsz; m_pszDatabase = psz; // bail out if file system pathname not specified if( !m_pwszDatabase || !*m_pwszDatabase ) return FALSE; // do a merge check if( m_bCollection ) { if( !MergeWordWheels() ) { m_bCollection = FALSE; m_pCollection = NULL; Free(); return Initialize(); } } // get ITDatabase ptr. if( SUCCEEDED(hr = CoCreateInstance(CLSID_IITDatabaseLocal, NULL, CLSCTX_INPROC_SERVER, IID_IITDatabase, (void**)&m_pDatabase) ) ) { // if the file exists then this is good enough to say we initialized it if( IsFile( m_pszDatabase ) ) { bReturn = TRUE; // Open the database if( SUCCEEDED(hr = m_pDatabase->Open(NULL, m_pwszDatabase, 0) ) ) { bReturn = TRUE; } } } // create our word wheels CTitleInformation* pInfo = NULL; if( (m_pTitle && (pInfo = m_pTitle->GetInfo())) || !m_pTitle ) { if( (m_pTitle && pInfo->IsKeywordLinks()) || (!m_pTitle && IsSubFile( m_pszDatabase, "$WWKeywordLinks\\Data" ) ) ) { m_pKeywordLinks = new CWordWheel( this, g_szKeywordLinks ); } if( (m_pTitle && pInfo->IsAssociativeLinks()) || (!m_pTitle && IsSubFile( m_pszDatabase, "$WWAssociativeLinks\\Data" ) ) ) { m_pAssociativeLinks = new CWordWheel( this, g_szAssociativeLinks ); } } if( !bReturn ) { Free(); m_bInit = FALSE; } else m_bInit = TRUE; return bReturn; } BOOL CTitleDatabase::Free() { BOOL bReturn = FALSE; if( m_pTitleMap ) { delete m_pTitleMap; m_pTitleMap = NULL; } if( m_pKeywordLinks ) { delete m_pKeywordLinks; m_pKeywordLinks = NULL; } if( m_pAssociativeLinks ) { delete m_pAssociativeLinks; m_pAssociativeLinks = NULL; } if( m_pDatabase ) { m_pDatabase->Close(); m_pDatabase->Release(); m_pDatabase = NULL; } if( m_pwszDatabase ) { delete [] (WCHAR*) m_pwszDatabase; m_pwszDatabase = NULL; } if( m_pszDatabase ) { delete [] (CHAR*) m_pszDatabase; m_pszDatabase = NULL; } m_bInit = FALSE; bReturn = TRUE; return bReturn; } BOOL CTitleDatabase::MergeWordWheels() { BOOL bReturn = FALSE; #ifndef CHIINDEX // get the application window int iTry = 0; HWND hWndApp = GetActiveWindow(); HWND hWndDesktop = GetDesktopWindow(); HWND hWnd = GetParent( hWndApp ); if( hWnd ) { while( hWnd != hWndDesktop ) { if( iTry++ == 16 ) { hWndApp = GetActiveWindow(); if( !IsValidWindow( hWndApp ) ) hWndApp = NULL; break; } hWndApp = hWnd; hWnd = GetParent( hWndApp ); } } #endif // start the animation #ifndef CHIINDEX if( !IsBusy() ) StartAnimation( IDS_CREATING_INDEX, hWndApp ); #endif // if another merge is in progress then wait // if we are currently busy then pretend to generate the file #ifndef CHIINDEX while( IsBusy() ) { Sleep( 100 ); NextAnimation(); } #endif // set the busy state SetBusy( TRUE ); // check if we need to merge or not BOOL bMerge = CheckWordWheels(); // do the merge if necessary if( bMerge ) { #if 0 // MsgBox causes a reentrant problem--so don't do this! if( !m_pTitle->GetInfo()->IsNeverPromptOnMerge() ) { if( MsgBox( IDS_MERGE_PROMPT, MB_OKCANCEL ) != IDOK ) bMerge = FALSE; } #endif if( bMerge ) { bReturn = BuildWordWheels(); } } else bReturn = TRUE; #ifndef CHIINDEX // stop the animation StopAnimation(); #endif // reset the busy state SetBusy( FALSE ); return bReturn; } BOOL CTitleDatabase::CheckWordWheels() { BOOL bReturn = FALSE; // // We perform a merge under the following conditions: // // 1. merged file does not exist. // 2. title count has changed // 3. any title has changed (updated, removed, added) BOOL bMerge = FALSE; DWORD dwCount = 0; CExTitle* pTitle = NULL; // We have to always create the title map even if we do not need to merge // first check if each title can be initialized // so we know how many real titles we have // (the collection may contain bogus entries so we need to exclude these) DWORD dwCount0 = m_pCollection->GetRefedTitleCount(); // create our title map of the existing files dwCount = dwCount0; m_pTitleMap = new CTitleMap; m_pTitleMap->SetCount( dwCount ); pTitle = m_pCollection->GetFirstTitle(); for( int iTitle = 0; iTitle < (int) dwCount; iTitle++ ) { CTitleMapEntry* pEntry = m_pTitleMap->GetAt( iTitle ); pEntry->SetTitle( pTitle ); if( pTitle ) pTitle = pTitle->GetNext(); } // 1. merged file does not exist. bMerge = !IsFile( m_pszDatabase ); if( !bMerge ) { // if we cannot read the file since read access is denied then wait // for the file to be readable again while( TRUE ) { HANDLE hFile = CreateFile( m_pszDatabase, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( hFile != INVALID_HANDLE_VALUE ) { CloseHandle( hFile ); break; } Sleep( 100 ); NextAnimation(); } // 2. title count has changed CTitleMap* pTitleMapSaved = new CTitleMap( m_pszDatabase ); DWORD dwCountSaved = pTitleMapSaved->GetCount(); bMerge = !( dwCountSaved == dwCount ); // 3. any title has changed (updated, removed, added) if( !bMerge ) { // walk the title list and compare it's title entries // to the one we just read in from the title map. // If they differ, then rebuild the merged file // otherwise, update the mapping Ids for the title map. for( int iTitleSaved = 0; iTitleSaved < (int) dwCountSaved; iTitleSaved++ ) { BOOL bMatch = FALSE; CTitleMapEntry* pEntrySaved = pTitleMapSaved->GetAt( iTitleSaved ); const CHAR* pszShortNameSaved = pEntrySaved->GetShortName(); FILETIME FileTimeSaved = pEntrySaved->GetFileTime(); LCID lcidSaved = pEntrySaved->GetLanguage(); DWORD dwIdSaved = pEntrySaved->GetId(); for( int iTitle = 0; iTitle < (int) dwCount; iTitle++ ) { CTitleMapEntry* pEntry = m_pTitleMap->GetAt( iTitle ); const CHAR* pszShortName = pEntry->GetShortName(); FILETIME FileTime = pEntry->GetFileTime(); LCID lcid = pEntry->GetLanguage(); // BUG 2807: the CHW file on the system had NULL for the "=pdobj" title. // This caused an access violation in SetShortName. [dalero] // A NULL short name will be saved to the CHW file, if we cannot find the CHM. if( pszShortNameSaved && !lstrcmpi( pszShortNameSaved, pszShortName ) ) { if( !CompareFileTime( &FileTimeSaved, &FileTime ) ) { if( lcidSaved == lcid ) { // Note, some dummy (MSDN) may have more than one copy of the identical // title but saved under a different filename. // // Thus, we need to make sure that each Id gets assigned to a title // and we do this by making sure the Id is not set already. // If it is set, the continue looking for the next free spot that // contains the same title information. if( !pEntry->GetId() ) { // empty Id pEntry->SetId( dwIdSaved ); // set the Id pEntry->SetShortName( pszShortNameSaved ); // set the Short Name bMatch = TRUE; break; } } } } } if( (bMerge = !bMatch) == TRUE ) break; } } delete pTitleMapSaved; } if( !bMerge ) { // sort the map entries in Id order m_pTitleMap->Sort( CompareIds ); } bReturn = bMerge; return bReturn; } BOOL CTitleDatabase::BuildWordWheels() { BOOL bReturn = FALSE; // delete existing file if( IsFile( m_pszDatabase ) ) DeleteFile( m_pszDatabase ); DWORD dwCount = m_pTitleMap->GetCount(); CExTitle* pTitle = NULL; // now merge the keywords (using the title map) CWordWheelCompiler* pCompiler = new CWordWheelCompiler( m_pszDatabase, g_wszKeywordLinks, g_wszAssociativeLinks ); if( pCompiler->Initialize() != S_OK ) return FALSE; IITBuildCollect* pBuildCollect = NULL; IITPropList* pPropList = pCompiler->m_pPropList; // #define HH_SLOW_MERGE // if you want merging to be slow #ifndef HH_SLOW_MERGE // create the property items upfront, and just update them as they change pPropList->Set(STDPROP_UID, (DWORD)0, PROP_ADD ); pPropList->Set(STDPROP_SORTKEY, (VOID*) NULL, 0, PROP_ADD ); #endif CWordWheel* pWordWheel = NULL; // set our title map Ids (in order), create our databases, // and create our word wheels for( int iTitleWalk = 0; iTitleWalk < (int) dwCount; iTitleWalk++ ) { #ifdef CHIINDEX if ( m_bAnimation ) { #endif NextAnimation(); Sleep(0); #ifdef CHIINDEX } #endif CTitleMapEntry* pEntry = m_pTitleMap->GetAt( iTitleWalk ); pEntry->SetId( iTitleWalk+1 ); CTitleDatabase* pDatabase = NULL; CWordWheel* pKeywordLinks = NULL; CWordWheel* pAssociativeLinks = NULL; pDatabase = new CTitleDatabase( pEntry->GetTitle()->GetIndexFileName() ); if( !pDatabase->Initialize() ) { delete pDatabase; pDatabase = NULL; // Can't do anything else without a CTitleDatabase. continue; } pEntry->SetDatabase( pDatabase ); pEntry->SetKeywordLinks( pDatabase->GetKeywordLinks() ); pEntry->SetAssociativeLinks( pDatabase->GetAssociativeLinks() ); // loop through each word wheel type for( int iWordWheel=1; iWordWheel<=2; iWordWheel++ ) { if( iWordWheel == 1 ) { pWordWheel = pDatabase->GetKeywordLinks(); pBuildCollect = pCompiler->m_pBuildCollectKeywordLinks; } if( iWordWheel == 2 ) { pWordWheel = pDatabase->GetAssociativeLinks(); pBuildCollect = pCompiler->m_pBuildCollectAssociativeLinks; } if( !pWordWheel ) continue; // add each keyword DWORD dwCount = pWordWheel->GetCount(); for( int iKeyword = 0; iKeyword < (int) dwCount; iKeyword++ ) { // update the animation if( !(iKeyword%200) ) { #ifdef CHIINDEX if (m_bAnimation) { #endif NextAnimation(); Sleep(0); #ifdef CHIINDEX } #endif } DWORD dwKeyword = iKeyword; BYTE KeywordObject[HHWW_MAX_KEYWORD_OBJECT_SIZE]; if( SUCCEEDED( pWordWheel->GetWordWheel()->Lookup( dwKeyword, KeywordObject, HHWW_MAX_KEYWORD_OBJECT_SIZE ) ) ) { int iLen = wcslen( (WCHAR*) KeywordObject ); int iOffset = sizeof(WCHAR) * (iLen+1); HHKEYINFO* pInfo = (HHKEYINFO*)(((DWORD_PTR)(&KeywordObject)+iOffset)); iOffset += sizeof(HHKEYINFO); if( pInfo->wFlags & HHWW_SEEALSO ) iOffset += sizeof(WCHAR) * (wcslen( (WCHAR*) (((DWORD_PTR)&KeywordObject)+iOffset) ) + 1); else { // change the UIDs to 12:20 format DWORD dwCount = pInfo->dwCount; UNALIGNED DWORD* pdwURLId = (DWORD*)(((DWORD_PTR)&KeywordObject)+iOffset); for( int iURLId = 0; iURLId < (int) dwCount; iURLId++ ) { DWORD dwURLId = *(pdwURLId+iURLId); dwURLId = ((iTitleWalk+1)<<20) + dwURLId; // 12:20 format *((UNALIGNED DWORD*)(((DWORD_PTR)&KeywordObject)+iOffset)) = dwURLId; iOffset += sizeof(DWORD); } } // add the new entry to the new word wheel // Note, we must set SZ_WWDEST_OCC and not SZ_WWDEST_KEY // otherwise ITCC crashes #ifdef HH_SLOW_MERGE pPropList->Set(STDPROP_UID, (DWORD)0, PROP_ADD ); pPropList->Set(STDPROP_SORTKEY, (VOID*) KeywordObject, iOffset, PROP_ADD ); pBuildCollect->SetEntry(SZ_WWDEST_OCC, pPropList); pPropList->Clear(); #else // simply update the property items -- no need to add/clear them each time pPropList->Set(STDPROP_SORTKEY, (VOID*) KeywordObject, iOffset, PROP_UPDATE ); pBuildCollect->SetEntry(SZ_WWDEST_OCC, pPropList); #endif } } } if( pDatabase ) { delete pDatabase; pEntry->SetDatabase( NULL ); } } #ifndef HH_SLOW_MERGE pPropList->Clear(); #endif // build it #ifdef CHIINDEX if ( m_bAnimation ) { #endif NextAnimation(); Sleep(0); #ifdef CHIINDEX } #endif pCompiler->Build(); // delete the compiler delete pCompiler; // open the database file CFileSystem* pDatabaseMap = new CFileSystem(); if( FAILED( pDatabaseMap->Init() ) || FAILED( pDatabaseMap->Open( m_pszDatabase, STGM_READWRITE | STGM_SHARE_EXCLUSIVE ) ) ) { // stop the animation StopAnimation(); SetBusy( FALSE ); return FALSE; } // sort the map entries in Id order m_pTitleMap->Sort( CompareIds ); // write out the new title map CSubFileSystem* pTitleMap = new CSubFileSystem( pDatabaseMap ); pTitleMap->CreateSub( g_szTitleMap ); // walk the collection and write out the map WORD wValue = (WORD) dwCount; pTitleMap->WriteSub( (const void*) &wValue, sizeof(wValue) ); for( int iTitle = 0; iTitle < (int) dwCount; iTitle++ ) { CTitleMapEntry* pEntry = m_pTitleMap->GetAt( iTitle ); const CHAR* pszShortName = pEntry->GetShortName(); // NOTE: pszShortName may be NULL. If the CHM file does not exists. It can be NULL!!! FILETIME FileTime = pEntry->GetFileTime(); LCID lcid = pEntry->GetLanguage(); ASSERT(pszShortName != NULL) ; // NOTE: If you get this assert it most likely means that the CHM file associated with this topic doesn't exist. DWORD dwLen = 0 ; if (pszShortName) { dwLen = (DWORD)strlen(pszShortName); } wValue = (WORD) dwLen; pTitleMap->WriteSub( (const void*) &wValue, sizeof(wValue) ); pTitleMap->WriteSub( pszShortName, (int) dwLen ); pTitleMap->WriteSub( (const void*) &FileTime, sizeof(FileTime) ); pTitleMap->WriteSub( (const void*) &lcid, sizeof(lcid) ); } delete pTitleMap; delete pDatabaseMap; bReturn = TRUE; return bReturn; } ///////////////////////////////////////////////////////////////////////////// // class CWordWheel implementation void CWordWheel::_CWordWheel() { m_bInit = FALSE; m_pWordWheel = NULL; m_dwCount = HHWW_ERROR; m_pszWordWheelIn = NULL; m_pwszWordWheelIn = NULL; m_pwszWordWheel = NULL; m_dwRefCount = 0; } CWordWheel::CWordWheel( CTitleDatabase* pDatabase, const WCHAR* pwszWordWheel, DWORD dwTitleId ) { _CWordWheel(); m_pDatabase = pDatabase; m_pwszWordWheelIn = pwszWordWheel; m_dwTitleId = dwTitleId; } CWordWheel::CWordWheel( CTitleDatabase* pDatabase, const CHAR* pszWordWheel, DWORD dwTitleId ) { _CWordWheel(); m_pDatabase = pDatabase; m_pszWordWheelIn = pszWordWheel; m_dwTitleId = dwTitleId; } CWordWheel::~CWordWheel() { Free(); } DWORD CWordWheel::AddRef() { return ++m_dwRefCount; } DWORD CWordWheel::Release() { if( m_dwRefCount ) --m_dwRefCount; return m_dwRefCount; } BOOL CWordWheel::Initialize() { // bail out if we are already initialized if( m_bInit ) return TRUE; // allocate a UNICODE version of our word wheel name if( m_pszWordWheelIn && *m_pszWordWheelIn ) { DWORD dwLen = (DWORD)strlen(m_pszWordWheelIn) + 1; m_pwszWordWheel = new WCHAR[dwLen+1]; MultiByteToWideChar(CP_ACP, 0, m_pszWordWheelIn, -1, (WCHAR*) m_pwszWordWheel, dwLen); } else if( m_pwszWordWheelIn && *m_pwszWordWheelIn ) { DWORD dwLen = wcslen(m_pwszWordWheelIn) + 1; m_pwszWordWheel = new WCHAR[dwLen]; wcscpy( (WCHAR*) m_pwszWordWheel, m_pwszWordWheelIn ); } // bail out if word wheel name not specified if( !m_pwszWordWheel || !*m_pwszWordWheel ) return FALSE; BOOL bReturn = FALSE; HRESULT hr = S_OK; // get ITWordWheel ptr. if( SUCCEEDED(hr = CoCreateInstance(CLSID_IITWordWheelLocal, NULL, CLSCTX_INPROC_SERVER, IID_IITWordWheel, (void**)&m_pWordWheel) ) ) { // open the word wheel if( SUCCEEDED(hr = m_pWordWheel->Open( m_pDatabase->GetDatabase(), m_pwszWordWheel, ITWW_OPEN_NOCONNECT) ) ) { bReturn = TRUE; } } if( !bReturn ) { Free(); m_bInit = FALSE; } else m_bInit = TRUE; return bReturn; } BOOL CWordWheel::Free() { BOOL bReturn = FALSE; if( m_pWordWheel ) { m_pWordWheel->Close(); m_pWordWheel->Release(); m_pWordWheel = NULL; } if( m_pwszWordWheel ) { delete [] (WCHAR*) m_pwszWordWheel; m_pwszWordWheel = NULL; } if( m_pwszWordWheelIn ) m_pwszWordWheelIn = NULL; if( m_pszWordWheelIn ) m_pszWordWheelIn = NULL; m_bInit = FALSE; bReturn = TRUE; return bReturn; } DWORD CWordWheel::GetCount() { DWORD dwReturn = HHWW_ERROR; if( Init() ) { if( m_dwCount == HHWW_ERROR ) { LONG nCount; if( SUCCEEDED(m_pWordWheel->Count(&nCount)) ) dwReturn = m_dwCount = nCount; } else dwReturn = m_dwCount; } return dwReturn; } ///////////////////////////////////////////////////////////////////////////// // CWordWheel::GetIndex // // params: // // pwszKeywordIn - keyword to lookup // // bFragment - set this to TRUE if only looking for a partial match // such as when the user types in a string fragment // in the Index tab // // pdwIndexLast - If not NULL, then the function should return the // first equivalent index and set the contents of this // argument to the index of the last equivalent keyword. // And equivalent keyword is where the root word (less // and special prefixes) is the same regardless of case. // For example, "_open", "open", "Open", and "OPEN" are // equivalent keywords. This should be used for F1 lookups // and A/KLink lookups as well. // // Note: if bFragment is TRUE and pdwIndexLast is non-NULL then // bFragment will be set to FALSE. If neither, then we look for an // exact match only. // DWORD CWordWheel::GetIndex( const WCHAR* pwszKeywordIn, BOOL bFragment, DWORD* pdwIndexLast ) { DWORD dwIndexFirst = HHWW_ERROR; // we cannot do both a fragment lookup and an equivalent lookup if( pdwIndexLast ) bFragment = FALSE; if( Init() && pwszKeywordIn && *pwszKeywordIn ) { if( GetCount() == (DWORD) -1 ) return dwIndexFirst; // since we are using a pluggable sort object the input to Lookup must // be in the save format as the sort object itself. That is, we need // to add an HHKEYINFO structure to the end of this string and fill in // the data properly since the CHHSysSort::GetSize function in our // pluggable sort module will get this object and expect in in that format // // Note, if it did not contain the trailing struct it can and will fault. BYTE KeywordObject[HHWW_MAX_KEYWORD_OBJECT_SIZE]; WCHAR* pwszKeywordObject = (WCHAR*) KeywordObject; wcsncpy( pwszKeywordObject, pwszKeywordIn, HHWW_MAX_KEYWORD_LENGTH ); pwszKeywordObject[HHWW_MAX_KEYWORD_LENGTH] = 0; #ifdef _DEBUG int iMaxObject = HHWW_MAX_KEYWORD_OBJECT_SIZE; int iMaxLen = HHWW_MAX_KEYWORD_LENGTH; int iLen = wcslen(pwszKeywordObject); #endif HHKEYINFO Info; Info.wFlags = 0; Info.wLevel = 0; Info.dwLevelOffset = 0; Info.dwFont = 0; Info.dwCount = 0; DWORD dwLength = sizeof(WCHAR) * (wcslen(pwszKeywordObject) + 1); *((HHKEYINFO*)(((DWORD_PTR)pwszKeywordObject)+dwLength)) = Info; // There are three kinds of lookup matches: // // 1. Exact - found hits must be completely indentical to the // keyword we are looking up. // "FOOBAR" == "FOOBAR" // // 2. Equivalent - the keyword must only differ by case or prefixes // and we continue to find all keywords, not just the // first one, that meets this criteria. // "_foobar" == "FooBar" == "FOOBAR" // // 3. Fragment - find the first hit where only the first non-prefixed // characters need to match while ignoring case. // "~fo" == "FOOBAR" (assuming nothing else was a closer match) // We have three ways that we perform such lookups: // // 1. Index tab - Tries Exact first then uses Fragment lookups. // // 2. F1 Lookups - Tries Exact first then uses Equivalent lookups. // // 3. A/Klinks - Tries Exact first then uses Equivalent lookups. // Centaur just doesn't do the right thing for non-exact, // a.k.a. prefix, lookups! // // For exact matches, try the first found keyword and verify it. // If it does not match, then try a fragment lookup. // // For fragment matches, we need to first try the exact match // technique noted above. // If it does not match, then try a partial (size of lookup word), // case-insensitive lookup. // If it does not match, and then try the *next* entry in the same fashion. BYTE KeywordObjectTry[HHWW_MAX_KEYWORD_OBJECT_SIZE]; WCHAR* pwszKeywordObjectTry = (WCHAR*) KeywordObjectTry; // Try exact matches first if( SUCCEEDED(m_pWordWheel->Lookup( &KeywordObject, (BOOL) TRUE, (LONG*) &dwIndexFirst )) ) { if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexFirst, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) { if( !(wcscmp( pwszKeywordObjectTry, pwszKeywordObject ) == 0) ) { dwIndexFirst = HHWW_ERROR; } } } // Try equivalent match next if( pdwIndexLast && (dwIndexFirst == HHWW_ERROR) ) { // skip over any special chars const WCHAR* pwszKeyword = NULL; for( pwszKeyword = pwszKeywordObject; pwszKeyword; pwszKeyword++ ) { if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) ) break; } // Try a prefix lookup if( SUCCEEDED(m_pWordWheel->Lookup( pwszKeyword, (BOOL) FALSE, (LONG*) &dwIndexFirst )) ) { if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexFirst, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) { // validate the hit, if it fails, try the next index value DWORD dwTryFirst = dwIndexFirst; dwIndexFirst = HHWW_ERROR; for( DWORD dwTry = dwTryFirst; dwTry <= dwTryFirst+1 ; dwTry++ ) { if( SUCCEEDED(m_pWordWheel->Lookup( dwTry, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) { // try it without prefixes in the input string if( wcsicmp( pwszKeywordObjectTry, pwszKeyword ) == 0 ) { dwIndexFirst = dwTry; break; } // try it with the prefixes back in else if( ((DWORD_PTR) pwszKeyword) != ((DWORD_PTR) pwszKeywordObject) ) { if( wcsicmp( pwszKeywordObjectTry, pwszKeywordObject ) == 0 ) { dwIndexFirst = dwTry; break; } } // ignore prefixes in found hit else if( (*pwszKeywordObjectTry == L'_') || (*pwszKeywordObjectTry == L'~') ) { const WCHAR* pwszKeyword = NULL; for( pwszKeyword = pwszKeywordObjectTry; pwszKeyword; pwszKeyword++ ) { if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) ) break; } if( wcsicmp( pwszKeywordObject, pwszKeyword ) == 0 ) { dwIndexFirst = dwTry; break; } } } } } } } // Try fragment match last if( bFragment && (dwIndexFirst == HHWW_ERROR) ) { // skip over any special chars const WCHAR* pwszKeyword = NULL; for( pwszKeyword = pwszKeywordObject; pwszKeyword; pwszKeyword++ ) { if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) ) break; } while( TRUE ) { if( SUCCEEDED(m_pWordWheel->Lookup( pwszKeyword, (BOOL) FALSE, (LONG*) &dwIndexFirst )) ) { int iLen = wcslen( pwszKeyword ); if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexFirst, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) { // validate the hit, if it fails, try the next index value DWORD dwTryFirst = dwIndexFirst; dwIndexFirst = HHWW_ERROR; for( DWORD dwTry = dwTryFirst; dwTry <= dwTryFirst+1; dwTry++ ) { if( SUCCEEDED(m_pWordWheel->Lookup( dwTry, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) { // try it without prefixes in the input string int iLen2 = min(iLen, (int) wcslen(pwszKeywordObjectTry)); if( wcsnicmp( pwszKeywordObjectTry, pwszKeyword, iLen2 ) == 0 ) { dwIndexFirst = dwTry; break; } // try it with the prefixes back in else if( ((DWORD_PTR) pwszKeyword) != ((DWORD_PTR) pwszKeywordObject) ) { int iLen = wcslen( pwszKeywordObject ); if( wcsnicmp( pwszKeywordObjectTry, pwszKeywordObject, iLen2 ) == 0 ) { dwIndexFirst = dwTry; break; } } // ignore prefixes in found hit else if( (*pwszKeywordObjectTry == L'_') || (*pwszKeywordObjectTry == L'~') ) { const WCHAR* pwszKeyword = NULL; for( pwszKeyword = pwszKeywordObjectTry; pwszKeyword; pwszKeyword++ ) { if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) ) break; } if( wcsnicmp( pwszKeywordObject, pwszKeyword, iLen2 ) == 0 ) { dwIndexFirst = dwTry; break; } } // for framgent lookups only, when all else fails, // simply trust the first try (works half the time) else if( dwTry == dwTryFirst+1 ) { dwIndexFirst = dwTryFirst; break; } } } } } // for fragments, we need to keep trying until we get a hit by // trimming the trailing chars one at a time until we get a match if( KeywordObject[0] && (dwIndexFirst == HHWW_ERROR) ) { DWORD dwLength = wcslen(pwszKeywordObject); if( dwLength <= 1 ) break; pwszKeywordObject[dwLength-1] = L'\0'; *((HHKEYINFO*)(((DWORD_PTR)KeywordObject)+(dwLength*sizeof(WCHAR)))) = Info; continue; } break; } } // if equivalent match found then find the real first and last equivalent if( pdwIndexLast && (dwIndexFirst != HHWW_ERROR) ) { DWORD dwIndexFirstTry = dwIndexFirst; DWORD dwIndexLastTry = dwIndexFirst; // skip over any special chars const WCHAR* pwszKeyword = NULL; for( pwszKeyword = pwszKeywordObject; pwszKeyword; pwszKeyword++ ) { if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) ) break; } // find the first one for( dwIndexFirstTry--; dwIndexFirstTry != (DWORD)-1; dwIndexFirstTry-- ) { if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexFirstTry, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) { const WCHAR* pwszBuffer = NULL; for( pwszBuffer = pwszKeywordObjectTry; pwszBuffer; pwszBuffer++ ) { if( !(( (*pwszBuffer) == L'_') || ( (*pwszBuffer) == L'~')) ) break; } if( !(_wcsicmp( pwszBuffer, pwszKeyword ) == 0) ) break; dwIndexFirst = dwIndexFirstTry; } } // find the last one *pdwIndexLast = dwIndexLastTry; for( dwIndexLastTry++; dwIndexLastTry <= m_dwCount; dwIndexLastTry++ ) { if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexLastTry, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) { const WCHAR* pwszBuffer = NULL; for( pwszBuffer = pwszKeywordObjectTry; pwszBuffer; pwszBuffer++ ) { if( !(( (*pwszBuffer) == L'_') || ( (*pwszBuffer) == L'~')) ) break; } if( !(_wcsicmp( pwszBuffer, pwszKeyword ) == 0) ) break; *pdwIndexLast = dwIndexLastTry; } } } } return dwIndexFirst; } DWORD CWordWheel::GetIndex( const CHAR* pszKeyword, BOOL bFragment, DWORD* pdwIndexLast ) { DWORD dwReturn = HHWW_ERROR; if( pszKeyword && *pszKeyword ) { WCHAR wszKeyword[HHWW_MAX_KEYWORD_LENGTH+1]; if( MultiByteToWideChar(CP_ACP, 0, pszKeyword, -1, wszKeyword, HHWW_MAX_KEYWORD_LENGTH+1) ) dwReturn = GetIndex( wszKeyword, bFragment, pdwIndexLast ); } return dwReturn; } BOOL CWordWheel::GetString( DWORD dwKeyword, WCHAR* pwszBuffer, DWORD cchBuffer, BOOL bFull, BOOL bCacheAll ) { BOOL bReturn = FALSE; if( pwszBuffer && cchBuffer>0 ) { if( (bReturn = GetIndexData( dwKeyword, bCacheAll ) ) ) { DWORD dwLength = 0; if( bFull ) dwLength = wcslen(m_CachedEntry.m_wszFullKeyword)+1; else dwLength = wcslen(m_CachedEntry.m_wszKeyword)+1; if( dwLength <= cchBuffer ) if( bFull ) wcscpy( pwszBuffer, m_CachedEntry.m_wszFullKeyword ); else wcscpy( pwszBuffer, m_CachedEntry.m_wszKeyword ); else *pwszBuffer = L'\0'; } } return bReturn; } BOOL CWordWheel::GetString( DWORD dwKeyword, CHAR* pszBuffer, DWORD cchBuffer, BOOL bFull, BOOL bCacheAll ) { BOOL bReturn = FALSE; if( pszBuffer && cchBuffer>0 ) { WCHAR wszBuffer[HHWW_MAX_KEYWORD_LENGTH+1]; if( bReturn = GetString( dwKeyword, wszBuffer, cchBuffer, bFull, bCacheAll ) ) { if( WideCharToMultiByte(CP_ACP, 0, wszBuffer, -1, pszBuffer, cchBuffer, NULL, NULL) == 0 ) bReturn = TRUE; } } return bReturn; } DWORD CWordWheel::GetLevel( DWORD dwKeyword ) { DWORD dwReturn = HHWW_ERROR; if( GetIndexData( dwKeyword ) ) dwReturn = m_CachedEntry.m_dwLevel; return dwReturn; } DWORD CWordWheel::GetLevelOffset( DWORD dwKeyword ) { DWORD dwReturn = HHWW_ERROR; if( GetIndexData( dwKeyword ) ) dwReturn = m_CachedEntry.m_dwLevelOffset; return dwReturn; } BOOL CWordWheel::IsPlaceHolder( DWORD dwKeyword ) { BOOL bReturn = FALSE; if( GetIndexData( dwKeyword ) ) { if( m_CachedEntry.m_dwFlags & HHWW_SEEALSO ) { if( wcscmp( m_CachedEntry.m_wszSeeAlso, m_CachedEntry.m_wszFullKeyword ) == 0 ) { bReturn = TRUE; } } } return bReturn; } BOOL CWordWheel::GetSeeAlso( DWORD dwKeyword, WCHAR* pwszBuffer, DWORD cchBuffer ) { BOOL bReturn = FALSE; if( pwszBuffer && cchBuffer>0 ) { if( (bReturn = GetIndexData( dwKeyword ) ) ) { if( m_CachedEntry.m_dwFlags & HHWW_SEEALSO ) { if( (DWORD) (wcslen(m_CachedEntry.m_wszSeeAlso)+1) <= cchBuffer ) wcscpy( pwszBuffer, m_CachedEntry.m_wszSeeAlso ); else *pwszBuffer = L'\0'; } else bReturn = FALSE; } } return bReturn; } BOOL CWordWheel::GetSeeAlso( DWORD dwKeyword, CHAR* pszBuffer, DWORD cchBuffer ) { BOOL bReturn = FALSE; if( pszBuffer && cchBuffer>0 ) { WCHAR wszBuffer[HHWW_MAX_KEYWORD_LENGTH+1]; if( bReturn = GetSeeAlso( dwKeyword, wszBuffer, cchBuffer ) ) { if( WideCharToMultiByte(CP_ACP, 0, wszBuffer, -1, pszBuffer, cchBuffer, NULL, NULL) == 0 ) bReturn = TRUE; } } return bReturn; } DWORD CWordWheel::GetHitCount( DWORD dwKeyword ) { DWORD dwReturn = HHWW_ERROR; if( GetIndexHitData( dwKeyword ) ) dwReturn = m_CachedResults.GetCount(); return dwReturn; } DWORD CWordWheel::GetHit( DWORD dwKeyword, DWORD dwHit, CExTitle** ppTitle ) { DWORD dwReturn = HHWW_ERROR; if( GetIndexHitData( dwKeyword ) ) { dwReturn = m_CachedResults.GetAt( dwHit )->GetURLId(); if( ppTitle != NULL ) *ppTitle = m_CachedResults.GetAt( dwHit )->GetTitle(); } return dwReturn; } inline BOOL CWordWheel::GetIndexHitData( const VOID* pcvKeywordObject, DWORD cbSize, HHKEYINFO* pInfo, DWORD dwKeyword ) { if( !((pInfo->wFlags) & HHWW_UID_OVERFLOW) && pInfo->dwCount ) { m_CachedResults.SetIndex( dwKeyword, pInfo->dwCount ); for( int i = 0; i < (int) pInfo->dwCount; i++ ) { DWORD dwURLId = *((UNALIGNED DWORD*) (((DWORD_PTR)pcvKeywordObject) + cbSize + (i*sizeof(DWORD))) ); CExTitle* pTitle = m_pDatabase->GetTitle(); // if we are reading a word wheel that is to be merged then // translate the URL Ids into 12/20 format if( m_dwTitleId ) { dwURLId = (m_dwTitleId<<20) + dwURLId; } // if we are reading a collection file then // translate the URL Ids to standard format // and set the title pointer appropriately else if( m_pDatabase->IsCollection() ) { DWORD dwTitleId = dwURLId>>20; // v1.1a creates bogus entries in the chw file. So // to workaround this we need to set the title id to 1 // and the url id to 0 when the title id is greater than // the title count if( dwTitleId > m_pDatabase->GetTitleMap()->GetCount() ) { dwTitleId = 1; dwURLId = 0; } else if( dwTitleId ) { dwURLId = dwURLId & 0x000FFFFF; pTitle = m_pDatabase->GetTitleMap()->GetAt(dwTitleId-1)->GetTitle(); } } m_CachedResults.GetAt( i )->SetURLId( dwURLId ); m_CachedResults.GetAt( i )->SetTitle( pTitle ); } } return TRUE; } BOOL CWordWheel::GetIndexData( DWORD dwKeyword, BOOL bCacheAll ) { BOOL bReturn = FALSE; if( Init() ) { if( m_CachedEntry.m_dwIndex != dwKeyword ) { BYTE KeywordObject[HHWW_MAX_KEYWORD_OBJECT_SIZE]; const VOID* pcvKeywordObject = KeywordObject; if( SUCCEEDED(m_pWordWheel->Lookup( dwKeyword, KeywordObject, sizeof(KeywordObject) ) ) ) { m_CachedEntry.m_dwIndex = dwKeyword; wcscpy( m_CachedEntry.m_wszFullKeyword, (WCHAR*) pcvKeywordObject ); DWORD cbSize = sizeof(WCHAR) * (wcslen((WCHAR*)pcvKeywordObject) + 1); HHKEYINFO* pInfo = (HHKEYINFO*)(((DWORD_PTR)(pcvKeywordObject))+cbSize); m_CachedEntry.m_dwFlags = (DWORD) pInfo->wFlags; m_CachedEntry.m_dwLevel = (DWORD) pInfo->wLevel; m_CachedEntry.m_dwLevelOffset = pInfo->dwLevelOffset; cbSize += sizeof(HHKEYINFO); if( pInfo->wLevel ) wcscpy( m_CachedEntry.m_wszKeyword, (WCHAR*) (((DWORD_PTR)pcvKeywordObject)+(pInfo->dwLevelOffset*sizeof(WCHAR))) ); else wcscpy( m_CachedEntry.m_wszKeyword, (WCHAR*) pcvKeywordObject ); if( (pInfo->wFlags) & HHWW_SEEALSO ) { wcscpy( m_CachedEntry.m_wszSeeAlso, (WCHAR*)(((DWORD_PTR)pcvKeywordObject)+cbSize) ); } if( bCacheAll ) { GetIndexHitData( pcvKeywordObject, cbSize, pInfo, dwKeyword ); } } } if( dwKeyword < GetCount() ) bReturn = TRUE; } return bReturn; } BOOL CWordWheel::GetIndexHitData( DWORD dwKeyword ) { BOOL bReturn = FALSE; if( Init() ) { if( m_CachedResults.GetIndex() != dwKeyword ) { BYTE KeywordObject[HHWW_MAX_KEYWORD_OBJECT_SIZE]; const VOID* pcvKeywordObject = KeywordObject; if( SUCCEEDED(m_pWordWheel->Lookup( dwKeyword, KeywordObject, sizeof(KeywordObject) ) ) ) { DWORD cbSize = sizeof(WCHAR) * (wcslen((WCHAR*)pcvKeywordObject) + 1); HHKEYINFO* pInfo = (HHKEYINFO*)(((DWORD_PTR)(pcvKeywordObject))+cbSize); cbSize += sizeof(HHKEYINFO); GetIndexHitData( pcvKeywordObject, cbSize, pInfo, dwKeyword ); } } if( dwKeyword < GetCount() ) bReturn = TRUE; } return bReturn; } ///////////////////////////////////////////////////////////////////////////// // class CWordWheelCompiler implementation void CWordWheelCompiler::_CWordWheelCompiler() { m_bInit = FALSE; m_pszDatabase = NULL; m_pwszKeywordLinks = NULL; m_pwszAssociativeLinks = NULL; m_lcid = 0; m_pFileSystem = NULL; m_pDatabase = NULL; m_pPersistStorageDatabase = NULL; m_pBuildCollectKeywordLinks = NULL; m_pBuildCollectAssociativeLinks = NULL; m_pPropList = NULL; m_pStorageKeywordLinks = NULL; m_pStorageAssociativeLinks = NULL; m_pPersistStorageKeywordLinks = NULL; m_pPersistStorageAssociativeLinks = NULL; } CWordWheelCompiler::CWordWheelCompiler( const CHAR* pszDatabase, const WCHAR* pwszKeywordLinks, const WCHAR* pwszAssociativeLinks, LCID lcid ) { _CWordWheelCompiler(); m_pszDatabase = pszDatabase; m_pwszKeywordLinks = pwszKeywordLinks; m_pwszAssociativeLinks = pwszAssociativeLinks; m_lcid = lcid; } CWordWheelCompiler::~CWordWheelCompiler() { Free(); } HRESULT CWordWheelCompiler::Initialize() { HRESULT hr = S_FALSE; // bail out if we are already initialized if( m_bInit ) return S_OK; // bail out if word wheel stream names not specified if( !m_pwszKeywordLinks || !*m_pwszKeywordLinks || !m_pwszAssociativeLinks || !*m_pwszAssociativeLinks ) return S_FALSE; // if the database is specified then use it otherwise generate // a temporary filename char szTempPath[MAX_PATH]; GetTempPath( sizeof(szTempPath), szTempPath ); if( m_pszDatabase && *m_pszDatabase ) { strcpy( m_szDatabase, m_pszDatabase ); } else { GetTempFileName( szTempPath,"TFS",0, m_szDatabase ); } // create the file system (delete the old one if it exists if( IsFile( m_szDatabase ) ) DeleteFile( m_szDatabase ); m_pFileSystem = new CFileSystem; if( !m_pFileSystem || FAILED( m_pFileSystem->Init() ) || FAILED( m_pFileSystem->CreateUncompressed( m_szDatabase ) ) ) { return S_FALSE; } // get ITDatabase ptr. if( SUCCEEDED(hr = CoCreateInstance(CLSID_IITDatabaseLocal, NULL, CLSCTX_INPROC_SERVER, IID_IITDatabase, (void**)&m_pDatabase) ) ) { if( SUCCEEDED(hr = m_pDatabase->QueryInterface( IID_IPersistStorage, (void**)&m_pPersistStorageDatabase ) ) ) { if( SUCCEEDED(hr = m_pPersistStorageDatabase->InitNew( m_pFileSystem->GetITStorageDocObj() ) ) ) { m_bInit = TRUE; } } } // create the sorter object DWORD dwSorterInstance; m_pDatabase->CreateObject( CLSID_HHSysSort, &dwSorterInstance ); // Create Build Collection objects if( SUCCEEDED(hr = CoCreateInstance( CLSID_IITWordWheelUpdate, NULL, CLSCTX_INPROC_SERVER, IID_IITBuildCollect, (VOID**)&m_pBuildCollectKeywordLinks ) ) ) { if( SUCCEEDED(hr = CoCreateInstance( CLSID_IITWordWheelUpdate, NULL, CLSCTX_INPROC_SERVER, IID_IITBuildCollect, (VOID**)&m_pBuildCollectAssociativeLinks ) ) ) { // Create keyword Property List (used for both AssociativeLinks and KeywordLinks) if( SUCCEEDED(hr = CoCreateInstance( CLSID_IITPropList, NULL, CLSCTX_INPROC_SERVER, IID_IITPropList, (VOID**)&m_pPropList) ) ) { m_bInit = TRUE; } } } // create the substorage files DWORD dwLen = 0; CHAR* psz = NULL; // KeywordLinks WCHAR wszKeywordLinks[MAX_PATH]; m_pBuildCollectKeywordLinks->GetTypeString( wszKeywordLinks, NULL ); // Get the "$WW" prefix wcscat( wszKeywordLinks, m_pwszKeywordLinks ); m_pFileSystem->GetITStorageDocObj()->CreateStorage( wszKeywordLinks, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &m_pStorageKeywordLinks); m_pBuildCollectKeywordLinks->QueryInterface( IID_IPersistStorage, (void**)&m_pPersistStorageKeywordLinks ); m_pPersistStorageKeywordLinks->InitNew( m_pStorageKeywordLinks ); // AssociativeLinks WCHAR wszAssociativeLinks[MAX_PATH]; m_pBuildCollectAssociativeLinks->GetTypeString( wszAssociativeLinks, NULL ); // Get the "$WW" prefix wcscat( wszAssociativeLinks, m_pwszAssociativeLinks ); m_pFileSystem->GetITStorageDocObj()->CreateStorage( wszAssociativeLinks, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &m_pStorageAssociativeLinks); m_pBuildCollectAssociativeLinks->QueryInterface( IID_IPersistStorage, (void**)&m_pPersistStorageAssociativeLinks ); m_pPersistStorageAssociativeLinks->InitNew( m_pStorageAssociativeLinks ); // get local information char szCodePage[20] = "1252"; if( m_lcid == ((DWORD)-1) ) m_lcid = GetSystemDefaultLCID(); GetLocaleInfo(m_lcid,LOCALE_IDEFAULTANSICODEPAGE,szCodePage,sizeof(szCodePage) ); DWORD dwCodePage = Atoi( szCodePage ); // apply sorter object information to the new word wheels VARARG vaDword = {0}; vaDword.dwArgc = 2; vaDword.Argv[0] = (void *)(INT_PTR)IHHSK100_KEYTYPE_UNICODE_SZ; vaDword.Argv[1] = (void*) 0; VARARG vaEmpty = {0}; m_pBuildCollectKeywordLinks->InitHelperInstance( dwSorterInstance, m_pDatabase, dwCodePage, m_lcid, vaDword, vaEmpty ); m_pBuildCollectAssociativeLinks->InitHelperInstance( dwSorterInstance, m_pDatabase, dwCodePage, m_lcid, vaDword, vaEmpty ); if( FAILED(hr) ) { Free(); m_bInit = FALSE; } else m_bInit = TRUE; return hr; } HRESULT CWordWheelCompiler::Free() { HRESULT hr = S_FALSE; if( m_pPersistStorageKeywordLinks ) { m_pPersistStorageKeywordLinks->Release(); m_pPersistStorageKeywordLinks = NULL; } if( m_pPersistStorageAssociativeLinks ) { m_pPersistStorageAssociativeLinks->Release(); m_pPersistStorageAssociativeLinks = NULL; } if( m_pStorageKeywordLinks ) { m_pStorageKeywordLinks->Release(); m_pStorageKeywordLinks = NULL; } if( m_pStorageAssociativeLinks ) { m_pStorageAssociativeLinks->Release(); m_pStorageAssociativeLinks = NULL; } if( m_pPropList ) { m_pPropList->Release(); m_pPropList = NULL; } if( m_pBuildCollectAssociativeLinks ) { m_pBuildCollectAssociativeLinks->Release(); m_pBuildCollectAssociativeLinks = NULL; } if( m_pBuildCollectKeywordLinks ) { m_pBuildCollectKeywordLinks->Release(); m_pBuildCollectKeywordLinks = NULL; } if( m_pPersistStorageDatabase ) { m_pPersistStorageDatabase->Release(); m_pPersistStorageDatabase = NULL; } if( m_pDatabase ) { m_pDatabase->Close(); m_pDatabase->Release(); m_pDatabase = NULL; } if( m_pFileSystem ) { delete m_pFileSystem; m_pFileSystem = NULL; } m_bInit = FALSE; hr = S_OK; return hr; } HRESULT CWordWheelCompiler::Build() { HRESULT hr = S_FALSE; if( Init() ) { #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); // KeywordLinks m_pPersistStorageKeywordLinks->Save( m_pStorageKeywordLinks, TRUE ); #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); m_pPersistStorageKeywordLinks->Release(); m_pPersistStorageKeywordLinks = NULL; #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); m_pStorageKeywordLinks->Commit(STGC_DEFAULT); #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); // AssociativeLinks m_pPersistStorageAssociativeLinks->Save( m_pStorageAssociativeLinks, TRUE ); #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); m_pPersistStorageAssociativeLinks->Release(); m_pPersistStorageAssociativeLinks = NULL; #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); m_pStorageAssociativeLinks->Commit(STGC_DEFAULT); #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); // Database == $OBJINST file m_pPersistStorageDatabase->Save( m_pFileSystem->GetITStorageDocObj(), TRUE ); #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); m_pPersistStorageDatabase->Release(); m_pPersistStorageDatabase = NULL; #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); m_pFileSystem->GetITStorageDocObj()->Commit(STGC_DEFAULT); #ifdef CHIINDEX if ( m_bAnimation ) #endif NextAnimation(); } return hr; }