Source code of Windows XP (NT5)
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.

1783 lines
52 KiB

  1. // WWheel.cpp - HTML Help Word Wheel support
  2. //
  3. // Covers both KeywordLinks and AssociativeLinks
  4. //
  5. //
  6. // needed for those pesky pre-compiled headers
  7. #include "header.h"
  8. #include "wwheel.h"
  9. #include "animate.h"
  10. #include "strtable.h"
  11. #include "resource.h"
  12. #include "util.h"
  13. // memory leak checks
  14. AUTO_CLASS_COUNT_CHECK(CTitleMapEntry);
  15. AUTO_CLASS_COUNT_CHECK(CTitleMap);
  16. AUTO_CLASS_COUNT_CHECK(CTitleDatabase);
  17. AUTO_CLASS_COUNT_CHECK(CResultsEntry);
  18. AUTO_CLASS_COUNT_CHECK(CResults);
  19. AUTO_CLASS_COUNT_CHECK(CWordWheelEntry);
  20. AUTO_CLASS_COUNT_CHECK(CWordWheel);
  21. AUTO_CLASS_COUNT_CHECK(CWordWheelCompiler);
  22. // taken from "hhsyssrt.h"
  23. // {4662dab0-d393-11d0-9a56-00c04fb68b66}
  24. // HACKHACK: I simply changed the last value of CLSID_ITSysSort from 0xf7 to 0x66
  25. DEFINE_GUID(CLSID_HHSysSort,
  26. 0x4662dab0, 0xd393, 0x11d0, 0x9a, 0x56, 0x00, 0xc0, 0x4f, 0xb6, 0x8b, 0x66);
  27. // old format
  28. #define IHHSK666_KEYTYPE_ANSI_SZ ((DWORD) 66630) // NULL-term. MBCS string + extra data
  29. #define IHHSK666_KEYTYPE_UNICODE_SZ ((DWORD) 66631) // NULL-term. Unicode string + extra data
  30. #if 0
  31. // New format
  32. #define IHHSK100_KEYTYPE_ANSI_SZ ((DWORD) 10030) // NULL-term. MBCS string + extra data
  33. #define IHHSK100_KEYTYPE_UNICODE_SZ ((DWORD) 10031) // NULL-term. Unicode string + extra data
  34. #endif
  35. #define IHHSK100_KEYTYPE_ANSI_SZ ((DWORD) 30) // NULL-term. MBCS string + extra data
  36. #define IHHSK100_KEYTYPE_UNICODE_SZ ((DWORD) 31) // NULL-term. Unicode string + extra data
  37. #ifdef _DEBUG
  38. #undef THIS_FILE
  39. static const char THIS_FILE[] = __FILE__;
  40. #endif
  41. // Global Variables
  42. static const CHAR g_szKeywordLinks[] = "KeywordLinks";
  43. static const CHAR g_szAssociativeLinks[] = "AssociativeLinks";
  44. static const CHAR g_szTitleMap[] = "$HHTitleMap";
  45. static const WCHAR g_wszKeywordLinks[] = L"KeywordLinks";
  46. static const WCHAR g_wszAssociativeLinks[] = L"AssociativeLinks";
  47. static const WCHAR g_wszTitleMap[] = L"$HHTitleMap";
  48. static const WCHAR g_wszError[] = L"(ERROR)";
  49. DWORD g_dwError = ((DWORD)-1);
  50. /////////////////////////////////////////////////////////////////////////////
  51. // helpful functions
  52. static const CHAR g_szBusyFile[] = "HTMLHelpKeywordMergingBusy";
  53. static HANDLE g_hFileBusy = NULL;
  54. #define BUSY_FILE_SIZE 32
  55. BOOL IsBusy()
  56. {
  57. BOOL bBusy = FALSE;
  58. HANDLE hFileBusy = NULL;
  59. SetLastError(0);
  60. hFileBusy = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READONLY, 0, BUSY_FILE_SIZE, g_szBusyFile );
  61. if( hFileBusy ) {
  62. if( GetLastError() == ERROR_ALREADY_EXISTS )
  63. bBusy = TRUE;
  64. CloseHandle( hFileBusy );
  65. }
  66. return bBusy;
  67. }
  68. void SetBusy( BOOL bBusy )
  69. {
  70. if( bBusy ) {
  71. if( !g_hFileBusy )
  72. g_hFileBusy = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READONLY, 0, BUSY_FILE_SIZE, g_szBusyFile );
  73. }
  74. else {
  75. if( g_hFileBusy ) {
  76. CloseHandle( g_hFileBusy );
  77. g_hFileBusy = NULL;
  78. }
  79. }
  80. }
  81. int FASTCALL CompareIds( const void* p1, const void* p2 )
  82. {
  83. int iReturn;
  84. CTitleMapEntry* pEntry1= (CTitleMapEntry*) p1;
  85. CTitleMapEntry* pEntry2= (CTitleMapEntry*) p2;
  86. DWORD dwId1 = pEntry1->GetId();
  87. DWORD dwId2 = pEntry2->GetId();
  88. if( dwId1 < dwId2 )
  89. iReturn = -1;
  90. else if ( dwId1 > dwId2 )
  91. iReturn = 1;
  92. else
  93. iReturn = 0;
  94. return iReturn;
  95. }
  96. // check if a specified subfile exists in the specified title
  97. BOOL IsSubFile( PCSTR pszTitlePathname, PCSTR pszSubFile )
  98. {
  99. BOOL bExists = FALSE;
  100. if( pszTitlePathname && pszTitlePathname[0] && pszSubFile && pszSubFile[0] ) {
  101. HRESULT hr = S_OK;
  102. CFileSystem* pFS = new CFileSystem;
  103. if( pFS && SUCCEEDED(hr = pFS->Init()) && SUCCEEDED(hr = pFS->Open( pszTitlePathname )) ) {
  104. CSubFileSystem* pSFS = new CSubFileSystem( pFS );
  105. if( pSFS && SUCCEEDED(pSFS->OpenSub(pszSubFile)) ) {
  106. bExists = TRUE;
  107. delete pSFS;
  108. }
  109. delete pFS;
  110. }
  111. }
  112. return bExists;
  113. }
  114. /////////////////////////////////////////////////////////////////////////////
  115. // class CTitleMap implementation
  116. BOOL CTitleMap::Initialize()
  117. {
  118. // bail out if we are already initialized
  119. if( m_bInit )
  120. return TRUE;
  121. // allocate a MBCS version of our file system pathname
  122. CHAR* psz = NULL;
  123. if( m_pszDatabase && *m_pszDatabase ) {
  124. DWORD dwLen = (DWORD)strlen(m_pszDatabase) + 1;
  125. psz = new char[dwLen];
  126. strcpy(psz,m_pszDatabase);
  127. }
  128. m_pszDatabase = psz;
  129. // set this bool first since we are going to call GetAt()
  130. // which will make a reentrant call if this is not set--
  131. // this would be bad!
  132. m_bInit = TRUE;
  133. // if we have a database, read in the data
  134. if( m_pszDatabase ) {
  135. HRESULT hr = S_OK;
  136. // open the database and read in the subfile
  137. CFileSystem* pDatabase = new CFileSystem;
  138. if( SUCCEEDED(hr = pDatabase->Init()) && SUCCEEDED(hr = pDatabase->Open( GetDatabase() )) ) {
  139. CSubFileSystem* pTitleMap = new CSubFileSystem( pDatabase );
  140. if( SUCCEEDED(hr = pTitleMap->OpenSub( g_szTitleMap ) ) ) {
  141. // format of TitleMap subfile is as follows:
  142. //
  143. // wCount (number of entries)
  144. // wShortNameLen, sShortName, FILETIME, LCID
  145. // ...line above repeated for each entry...
  146. //
  147. ULONG cbRead = 0;
  148. WORD wCount = 0;
  149. pTitleMap->ReadSub( (void*) &wCount, sizeof(wCount), &cbRead );
  150. SetCount( (DWORD) wCount );
  151. for( int iCount = 0; iCount < (int) wCount; iCount++ ) {
  152. WORD wLen = 0;
  153. char szShortName[256];
  154. FILETIME FileTime;
  155. LCID lcid;
  156. pTitleMap->ReadSub( (void*) &wLen, sizeof(wLen), &cbRead );
  157. pTitleMap->ReadSub( (void*) &szShortName, wLen, &cbRead );
  158. szShortName[wLen] = 0;
  159. ASSERT(cbRead != 0) ; // See HH Bug 2807 --- Saved a NULL shortname.
  160. // This means that the CHM file associted with this
  161. // topic probably doesn't exist. Tell the owner of the collection.
  162. pTitleMap->ReadSub( (void*) &FileTime, sizeof(FileTime), &cbRead );
  163. pTitleMap->ReadSub( (void*) &lcid, sizeof(lcid), &cbRead );
  164. GetAt((DWORD)iCount)->SetId( iCount+1 );
  165. GetAt((DWORD)iCount)->SetShortName( szShortName );
  166. GetAt((DWORD)iCount)->SetFileTime( FileTime );
  167. GetAt((DWORD)iCount)->SetLanguage( lcid );
  168. }
  169. }
  170. delete pTitleMap;
  171. }
  172. delete pDatabase;
  173. }
  174. else
  175. m_bInit = FALSE;
  176. return m_bInit;
  177. }
  178. BOOL CTitleMap::Free()
  179. {
  180. if( m_pEntries ) {
  181. delete [] m_pEntries;
  182. m_pEntries = NULL;
  183. m_dwCount = HHWW_ERROR;
  184. }
  185. if( m_pszDatabase ) {
  186. delete [] (CHAR*) m_pszDatabase;
  187. m_pszDatabase = NULL;
  188. }
  189. m_bInit = FALSE;
  190. return TRUE;
  191. }
  192. /////////////////////////////////////////////////////////////////////////////
  193. // class CTitleDatabase implementation (Shared Centaur object)
  194. void CTitleDatabase::_CTitleDatabase()
  195. {
  196. m_bInit = FALSE;
  197. m_pDatabase = NULL;
  198. m_pwszDatabase = NULL;
  199. m_pszDatabase = NULL;
  200. m_bCollection = FALSE;
  201. m_pTitleMap = NULL;
  202. m_pKeywordLinks = NULL;
  203. m_pAssociativeLinks = NULL;
  204. m_pCollection = NULL;
  205. m_pTitle = NULL;
  206. #ifdef CHIINDEX
  207. m_bAnimation = TRUE; // display animation
  208. #endif
  209. }
  210. CTitleDatabase::CTitleDatabase( CExCollection* pCollection )
  211. {
  212. _CTitleDatabase();
  213. m_pCollection = pCollection;
  214. }
  215. CTitleDatabase::CTitleDatabase( CExTitle* pTitle )
  216. {
  217. _CTitleDatabase();
  218. m_pTitle = pTitle;
  219. }
  220. CTitleDatabase::CTitleDatabase( const WCHAR* pwszDatabase )
  221. {
  222. _CTitleDatabase();
  223. m_pwszDatabase = pwszDatabase;
  224. }
  225. CTitleDatabase::CTitleDatabase( const CHAR* pszDatabase )
  226. {
  227. _CTitleDatabase();
  228. m_pszDatabase = pszDatabase;
  229. }
  230. CTitleDatabase::~CTitleDatabase()
  231. {
  232. Free();
  233. }
  234. BOOL CTitleDatabase::Initialize(CHAR *pszFileName)
  235. {
  236. BOOL bReturn = FALSE;
  237. HRESULT hr = S_OK;
  238. // bail out if we are already initialized
  239. if( m_bInit )
  240. return TRUE;
  241. // bail out if collection or title not specified
  242. if( !(m_pCollection || m_pTitle || m_pwszDatabase || m_pszDatabase ) )
  243. return FALSE;
  244. // set the hourglass cursor
  245. CHourGlass HourGlass;
  246. // get the database name
  247. if( m_pCollection ) {
  248. m_pTitle = m_pCollection->GetFirstTitle();
  249. m_bCollection = ( m_pCollection && (m_pCollection->GetRefedTitleCount() > 1) );
  250. if( m_bCollection )
  251. m_pszDatabase = m_pCollection->GetLocalStoragePathname(".chw");
  252. else
  253. m_pszDatabase = m_pTitle->GetIndexFileName();
  254. }
  255. else if( m_pTitle ) {
  256. m_pszDatabase = m_pTitle->GetIndexFileName();
  257. }
  258. if (pszFileName)
  259. {
  260. char drive[_MAX_PATH], dir[_MAX_PATH];
  261. splitpath(m_pszDatabase, drive, dir, NULL, NULL);
  262. if (drive[0] && drive[strlen(drive)-1] != '\\' && dir[0] != '\\')
  263. strcat(drive, "\\");
  264. strcat(drive, dir);
  265. if (drive[strlen(drive)-1] != '\\' && pszFileName[0] != '\\')
  266. strcat(drive, "\\");
  267. strcat(drive, pszFileName);
  268. strcpy(m_szFullPath, drive);
  269. m_pszDatabase = m_szFullPath;
  270. }
  271. // allocate UNICODE and MBCS versions of our file system pathname
  272. WCHAR* pwsz = NULL;
  273. CHAR* psz = NULL;
  274. if( m_pszDatabase && *m_pszDatabase ) {
  275. DWORD dwLen = (DWORD)strlen(m_pszDatabase) + 1;
  276. pwsz = new WCHAR[dwLen];
  277. MultiByteToWideChar(CP_ACP, 0, m_pszDatabase, -1, pwsz, dwLen);
  278. psz = new char[dwLen];
  279. strcpy(psz,m_pszDatabase);
  280. }
  281. else if( m_pwszDatabase && *m_pwszDatabase ) {
  282. DWORD dwLen = wcslen(m_pwszDatabase) + 1;
  283. pwsz = new WCHAR[dwLen];
  284. wcscpy(pwsz,m_pwszDatabase);
  285. psz = new char[dwLen];
  286. WideCharToMultiByte(CP_ACP, 0, m_pwszDatabase, -1, psz, dwLen, NULL, NULL);
  287. }
  288. m_pwszDatabase = pwsz;
  289. m_pszDatabase = psz;
  290. // bail out if file system pathname not specified
  291. if( !m_pwszDatabase || !*m_pwszDatabase )
  292. return FALSE;
  293. // do a merge check
  294. if( m_bCollection ) {
  295. if( !MergeWordWheels() ) {
  296. m_bCollection = FALSE;
  297. m_pCollection = NULL;
  298. Free();
  299. return Initialize();
  300. }
  301. }
  302. // get ITDatabase ptr.
  303. if( SUCCEEDED(hr = CoCreateInstance(CLSID_IITDatabaseLocal, NULL, CLSCTX_INPROC_SERVER,
  304. IID_IITDatabase, (void**)&m_pDatabase) ) ) {
  305. // if the file exists then this is good enough to say we initialized it
  306. if( IsFile( m_pszDatabase ) ) {
  307. bReturn = TRUE;
  308. // Open the database
  309. if( SUCCEEDED(hr = m_pDatabase->Open(NULL, m_pwszDatabase, 0) ) ) {
  310. bReturn = TRUE;
  311. }
  312. }
  313. }
  314. // create our word wheels
  315. CTitleInformation* pInfo = NULL;
  316. if( (m_pTitle && (pInfo = m_pTitle->GetInfo())) || !m_pTitle ) {
  317. if( (m_pTitle && pInfo->IsKeywordLinks()) ||
  318. (!m_pTitle && IsSubFile( m_pszDatabase, "$WWKeywordLinks\\Data" ) ) ) {
  319. m_pKeywordLinks = new CWordWheel( this, g_szKeywordLinks );
  320. }
  321. if( (m_pTitle && pInfo->IsAssociativeLinks()) ||
  322. (!m_pTitle && IsSubFile( m_pszDatabase, "$WWAssociativeLinks\\Data" ) ) ) {
  323. m_pAssociativeLinks = new CWordWheel( this, g_szAssociativeLinks );
  324. }
  325. }
  326. if( !bReturn ) {
  327. Free();
  328. m_bInit = FALSE;
  329. }
  330. else
  331. m_bInit = TRUE;
  332. return bReturn;
  333. }
  334. BOOL CTitleDatabase::Free()
  335. {
  336. BOOL bReturn = FALSE;
  337. if( m_pTitleMap ) {
  338. delete m_pTitleMap;
  339. m_pTitleMap = NULL;
  340. }
  341. if( m_pKeywordLinks ) {
  342. delete m_pKeywordLinks;
  343. m_pKeywordLinks = NULL;
  344. }
  345. if( m_pAssociativeLinks ) {
  346. delete m_pAssociativeLinks;
  347. m_pAssociativeLinks = NULL;
  348. }
  349. if( m_pDatabase ) {
  350. m_pDatabase->Close();
  351. m_pDatabase->Release();
  352. m_pDatabase = NULL;
  353. }
  354. if( m_pwszDatabase ) {
  355. delete [] (WCHAR*) m_pwszDatabase;
  356. m_pwszDatabase = NULL;
  357. }
  358. if( m_pszDatabase ) {
  359. delete [] (CHAR*) m_pszDatabase;
  360. m_pszDatabase = NULL;
  361. }
  362. m_bInit = FALSE;
  363. bReturn = TRUE;
  364. return bReturn;
  365. }
  366. BOOL CTitleDatabase::MergeWordWheels()
  367. {
  368. BOOL bReturn = FALSE;
  369. #ifndef CHIINDEX
  370. // get the application window
  371. int iTry = 0;
  372. HWND hWndApp = GetActiveWindow();
  373. HWND hWndDesktop = GetDesktopWindow();
  374. HWND hWnd = GetParent( hWndApp );
  375. if( hWnd ) {
  376. while( hWnd != hWndDesktop ) {
  377. if( iTry++ == 16 ) {
  378. hWndApp = GetActiveWindow();
  379. if( !IsValidWindow( hWndApp ) )
  380. hWndApp = NULL;
  381. break;
  382. }
  383. hWndApp = hWnd;
  384. hWnd = GetParent( hWndApp );
  385. }
  386. }
  387. #endif
  388. // start the animation
  389. #ifndef CHIINDEX
  390. if( !IsBusy() )
  391. StartAnimation( IDS_CREATING_INDEX, hWndApp );
  392. #endif
  393. // if another merge is in progress then wait
  394. // if we are currently busy then pretend to generate the file
  395. #ifndef CHIINDEX
  396. while( IsBusy() ) {
  397. Sleep( 100 );
  398. NextAnimation();
  399. }
  400. #endif
  401. // set the busy state
  402. SetBusy( TRUE );
  403. // check if we need to merge or not
  404. BOOL bMerge = CheckWordWheels();
  405. // do the merge if necessary
  406. if( bMerge ) {
  407. #if 0 // MsgBox causes a reentrant problem--so don't do this!
  408. if( !m_pTitle->GetInfo()->IsNeverPromptOnMerge() ) {
  409. if( MsgBox( IDS_MERGE_PROMPT, MB_OKCANCEL ) != IDOK )
  410. bMerge = FALSE;
  411. }
  412. #endif
  413. if( bMerge ) {
  414. bReturn = BuildWordWheels();
  415. }
  416. }
  417. else
  418. bReturn = TRUE;
  419. #ifndef CHIINDEX
  420. // stop the animation
  421. StopAnimation();
  422. #endif
  423. // reset the busy state
  424. SetBusy( FALSE );
  425. return bReturn;
  426. }
  427. BOOL CTitleDatabase::CheckWordWheels()
  428. {
  429. BOOL bReturn = FALSE;
  430. //
  431. // We perform a merge under the following conditions:
  432. //
  433. // 1. merged file does not exist.
  434. // 2. title count has changed
  435. // 3. any title has changed (updated, removed, added)
  436. BOOL bMerge = FALSE;
  437. DWORD dwCount = 0;
  438. CExTitle* pTitle = NULL;
  439. // We have to always create the title map even if we do not need to merge
  440. // first check if each title can be initialized
  441. // so we know how many real titles we have
  442. // (the collection may contain bogus entries so we need to exclude these)
  443. DWORD dwCount0 = m_pCollection->GetRefedTitleCount();
  444. // create our title map of the existing files
  445. dwCount = dwCount0;
  446. m_pTitleMap = new CTitleMap;
  447. m_pTitleMap->SetCount( dwCount );
  448. pTitle = m_pCollection->GetFirstTitle();
  449. for( int iTitle = 0; iTitle < (int) dwCount; iTitle++ ) {
  450. CTitleMapEntry* pEntry = m_pTitleMap->GetAt( iTitle );
  451. pEntry->SetTitle( pTitle );
  452. if( pTitle )
  453. pTitle = pTitle->GetNext();
  454. }
  455. // 1. merged file does not exist.
  456. bMerge = !IsFile( m_pszDatabase );
  457. if( !bMerge ) {
  458. // if we cannot read the file since read access is denied then wait
  459. // for the file to be readable again
  460. while( TRUE ) {
  461. HANDLE hFile = CreateFile( m_pszDatabase, GENERIC_READ, FILE_SHARE_READ,
  462. NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
  463. if( hFile != INVALID_HANDLE_VALUE ) {
  464. CloseHandle( hFile );
  465. break;
  466. }
  467. Sleep( 100 );
  468. NextAnimation();
  469. }
  470. // 2. title count has changed
  471. CTitleMap* pTitleMapSaved = new CTitleMap( m_pszDatabase );
  472. DWORD dwCountSaved = pTitleMapSaved->GetCount();
  473. bMerge = !( dwCountSaved == dwCount );
  474. // 3. any title has changed (updated, removed, added)
  475. if( !bMerge ) {
  476. // walk the title list and compare it's title entries
  477. // to the one we just read in from the title map.
  478. // If they differ, then rebuild the merged file
  479. // otherwise, update the mapping Ids for the title map.
  480. for( int iTitleSaved = 0; iTitleSaved < (int) dwCountSaved; iTitleSaved++ ) {
  481. BOOL bMatch = FALSE;
  482. CTitleMapEntry* pEntrySaved = pTitleMapSaved->GetAt( iTitleSaved );
  483. const CHAR* pszShortNameSaved = pEntrySaved->GetShortName();
  484. FILETIME FileTimeSaved = pEntrySaved->GetFileTime();
  485. LCID lcidSaved = pEntrySaved->GetLanguage();
  486. DWORD dwIdSaved = pEntrySaved->GetId();
  487. for( int iTitle = 0; iTitle < (int) dwCount; iTitle++ ) {
  488. CTitleMapEntry* pEntry = m_pTitleMap->GetAt( iTitle );
  489. const CHAR* pszShortName = pEntry->GetShortName();
  490. FILETIME FileTime = pEntry->GetFileTime();
  491. LCID lcid = pEntry->GetLanguage();
  492. // BUG 2807: the CHW file on the system had NULL for the "=pdobj" title.
  493. // This caused an access violation in SetShortName. [dalero]
  494. // A NULL short name will be saved to the CHW file, if we cannot find the CHM.
  495. if( pszShortNameSaved && !lstrcmpi( pszShortNameSaved, pszShortName ) ) {
  496. if( !CompareFileTime( &FileTimeSaved, &FileTime ) ) {
  497. if( lcidSaved == lcid ) {
  498. // Note, some dummy (MSDN) may have more than one copy of the identical
  499. // title but saved under a different filename.
  500. //
  501. // Thus, we need to make sure that each Id gets assigned to a title
  502. // and we do this by making sure the Id is not set already.
  503. // If it is set, the continue looking for the next free spot that
  504. // contains the same title information.
  505. if( !pEntry->GetId() ) { // empty Id
  506. pEntry->SetId( dwIdSaved ); // set the Id
  507. pEntry->SetShortName( pszShortNameSaved ); // set the Short Name
  508. bMatch = TRUE;
  509. break;
  510. }
  511. }
  512. }
  513. }
  514. }
  515. if( (bMerge = !bMatch) == TRUE )
  516. break;
  517. }
  518. }
  519. delete pTitleMapSaved;
  520. }
  521. if( !bMerge ) {
  522. // sort the map entries in Id order
  523. m_pTitleMap->Sort( CompareIds );
  524. }
  525. bReturn = bMerge;
  526. return bReturn;
  527. }
  528. BOOL CTitleDatabase::BuildWordWheels()
  529. {
  530. BOOL bReturn = FALSE;
  531. // delete existing file
  532. if( IsFile( m_pszDatabase ) )
  533. DeleteFile( m_pszDatabase );
  534. DWORD dwCount = m_pTitleMap->GetCount();
  535. CExTitle* pTitle = NULL;
  536. // now merge the keywords (using the title map)
  537. CWordWheelCompiler* pCompiler = new CWordWheelCompiler( m_pszDatabase,
  538. g_wszKeywordLinks, g_wszAssociativeLinks );
  539. if( pCompiler->Initialize() != S_OK )
  540. return FALSE;
  541. IITBuildCollect* pBuildCollect = NULL;
  542. IITPropList* pPropList = pCompiler->m_pPropList;
  543. // #define HH_SLOW_MERGE // if you want merging to be slow
  544. #ifndef HH_SLOW_MERGE
  545. // create the property items upfront, and just update them as they change
  546. pPropList->Set(STDPROP_UID, (DWORD)0, PROP_ADD );
  547. pPropList->Set(STDPROP_SORTKEY, (VOID*) NULL, 0, PROP_ADD );
  548. #endif
  549. CWordWheel* pWordWheel = NULL;
  550. // set our title map Ids (in order), create our databases,
  551. // and create our word wheels
  552. for( int iTitleWalk = 0; iTitleWalk < (int) dwCount; iTitleWalk++ )
  553. {
  554. #ifdef CHIINDEX
  555. if ( m_bAnimation )
  556. {
  557. #endif
  558. NextAnimation();
  559. Sleep(0);
  560. #ifdef CHIINDEX
  561. }
  562. #endif
  563. CTitleMapEntry* pEntry = m_pTitleMap->GetAt( iTitleWalk );
  564. pEntry->SetId( iTitleWalk+1 );
  565. CTitleDatabase* pDatabase = NULL;
  566. CWordWheel* pKeywordLinks = NULL;
  567. CWordWheel* pAssociativeLinks = NULL;
  568. pDatabase = new CTitleDatabase( pEntry->GetTitle()->GetIndexFileName() );
  569. if( !pDatabase->Initialize() ) {
  570. delete pDatabase;
  571. pDatabase = NULL;
  572. // Can't do anything else without a CTitleDatabase.
  573. continue;
  574. }
  575. pEntry->SetDatabase( pDatabase );
  576. pEntry->SetKeywordLinks( pDatabase->GetKeywordLinks() );
  577. pEntry->SetAssociativeLinks( pDatabase->GetAssociativeLinks() );
  578. // loop through each word wheel type
  579. for( int iWordWheel=1; iWordWheel<=2; iWordWheel++ ) {
  580. if( iWordWheel == 1 ) {
  581. pWordWheel = pDatabase->GetKeywordLinks();
  582. pBuildCollect = pCompiler->m_pBuildCollectKeywordLinks;
  583. }
  584. if( iWordWheel == 2 ) {
  585. pWordWheel = pDatabase->GetAssociativeLinks();
  586. pBuildCollect = pCompiler->m_pBuildCollectAssociativeLinks;
  587. }
  588. if( !pWordWheel )
  589. continue;
  590. // add each keyword
  591. DWORD dwCount = pWordWheel->GetCount();
  592. for( int iKeyword = 0; iKeyword < (int) dwCount; iKeyword++ ) {
  593. // update the animation
  594. if( !(iKeyword%200) ) {
  595. #ifdef CHIINDEX
  596. if (m_bAnimation) {
  597. #endif
  598. NextAnimation();
  599. Sleep(0);
  600. #ifdef CHIINDEX
  601. }
  602. #endif
  603. }
  604. DWORD dwKeyword = iKeyword;
  605. BYTE KeywordObject[HHWW_MAX_KEYWORD_OBJECT_SIZE];
  606. if( SUCCEEDED( pWordWheel->GetWordWheel()->Lookup( dwKeyword, KeywordObject, HHWW_MAX_KEYWORD_OBJECT_SIZE ) ) ) {
  607. int iLen = wcslen( (WCHAR*) KeywordObject );
  608. int iOffset = sizeof(WCHAR) * (iLen+1);
  609. HHKEYINFO* pInfo = (HHKEYINFO*)(((DWORD_PTR)(&KeywordObject)+iOffset));
  610. iOffset += sizeof(HHKEYINFO);
  611. if( pInfo->wFlags & HHWW_SEEALSO )
  612. iOffset += sizeof(WCHAR) * (wcslen( (WCHAR*) (((DWORD_PTR)&KeywordObject)+iOffset) ) + 1);
  613. else { // change the UIDs to 12:20 format
  614. DWORD dwCount = pInfo->dwCount;
  615. UNALIGNED DWORD* pdwURLId = (DWORD*)(((DWORD_PTR)&KeywordObject)+iOffset);
  616. for( int iURLId = 0; iURLId < (int) dwCount; iURLId++ ) {
  617. DWORD dwURLId = *(pdwURLId+iURLId);
  618. dwURLId = ((iTitleWalk+1)<<20) + dwURLId; // 12:20 format
  619. *((UNALIGNED DWORD*)(((DWORD_PTR)&KeywordObject)+iOffset)) = dwURLId;
  620. iOffset += sizeof(DWORD);
  621. }
  622. }
  623. // add the new entry to the new word wheel
  624. // Note, we must set SZ_WWDEST_OCC and not SZ_WWDEST_KEY
  625. // otherwise ITCC crashes
  626. #ifdef HH_SLOW_MERGE
  627. pPropList->Set(STDPROP_UID, (DWORD)0, PROP_ADD );
  628. pPropList->Set(STDPROP_SORTKEY, (VOID*) KeywordObject, iOffset, PROP_ADD );
  629. pBuildCollect->SetEntry(SZ_WWDEST_OCC, pPropList);
  630. pPropList->Clear();
  631. #else
  632. // simply update the property items -- no need to add/clear them each time
  633. pPropList->Set(STDPROP_SORTKEY, (VOID*) KeywordObject, iOffset, PROP_UPDATE );
  634. pBuildCollect->SetEntry(SZ_WWDEST_OCC, pPropList);
  635. #endif
  636. }
  637. }
  638. }
  639. if( pDatabase ) {
  640. delete pDatabase;
  641. pEntry->SetDatabase( NULL );
  642. }
  643. }
  644. #ifndef HH_SLOW_MERGE
  645. pPropList->Clear();
  646. #endif
  647. // build it
  648. #ifdef CHIINDEX
  649. if ( m_bAnimation )
  650. {
  651. #endif
  652. NextAnimation();
  653. Sleep(0);
  654. #ifdef CHIINDEX
  655. }
  656. #endif
  657. pCompiler->Build();
  658. // delete the compiler
  659. delete pCompiler;
  660. // open the database file
  661. CFileSystem* pDatabaseMap = new CFileSystem();
  662. if( FAILED( pDatabaseMap->Init() ) || FAILED( pDatabaseMap->Open( m_pszDatabase, STGM_READWRITE | STGM_SHARE_EXCLUSIVE ) ) ) {
  663. // stop the animation
  664. StopAnimation();
  665. SetBusy( FALSE );
  666. return FALSE;
  667. }
  668. // sort the map entries in Id order
  669. m_pTitleMap->Sort( CompareIds );
  670. // write out the new title map
  671. CSubFileSystem* pTitleMap = new CSubFileSystem( pDatabaseMap );
  672. pTitleMap->CreateSub( g_szTitleMap );
  673. // walk the collection and write out the map
  674. WORD wValue = (WORD) dwCount;
  675. pTitleMap->WriteSub( (const void*) &wValue, sizeof(wValue) );
  676. for( int iTitle = 0; iTitle < (int) dwCount; iTitle++ ) {
  677. CTitleMapEntry* pEntry = m_pTitleMap->GetAt( iTitle );
  678. const CHAR* pszShortName = pEntry->GetShortName(); // NOTE: pszShortName may be NULL. If the CHM file does not exists. It can be NULL!!!
  679. FILETIME FileTime = pEntry->GetFileTime();
  680. LCID lcid = pEntry->GetLanguage();
  681. ASSERT(pszShortName != NULL) ; // NOTE: If you get this assert it most likely means that the CHM file associated with this topic doesn't exist.
  682. DWORD dwLen = 0 ;
  683. if (pszShortName)
  684. {
  685. dwLen = (DWORD)strlen(pszShortName);
  686. }
  687. wValue = (WORD) dwLen;
  688. pTitleMap->WriteSub( (const void*) &wValue, sizeof(wValue) );
  689. pTitleMap->WriteSub( pszShortName, (int) dwLen );
  690. pTitleMap->WriteSub( (const void*) &FileTime, sizeof(FileTime) );
  691. pTitleMap->WriteSub( (const void*) &lcid, sizeof(lcid) );
  692. }
  693. delete pTitleMap;
  694. delete pDatabaseMap;
  695. bReturn = TRUE;
  696. return bReturn;
  697. }
  698. /////////////////////////////////////////////////////////////////////////////
  699. // class CWordWheel implementation
  700. void CWordWheel::_CWordWheel()
  701. {
  702. m_bInit = FALSE;
  703. m_pWordWheel = NULL;
  704. m_dwCount = HHWW_ERROR;
  705. m_pszWordWheelIn = NULL;
  706. m_pwszWordWheelIn = NULL;
  707. m_pwszWordWheel = NULL;
  708. m_dwRefCount = 0;
  709. }
  710. CWordWheel::CWordWheel( CTitleDatabase* pDatabase, const WCHAR* pwszWordWheel, DWORD dwTitleId )
  711. {
  712. _CWordWheel();
  713. m_pDatabase = pDatabase;
  714. m_pwszWordWheelIn = pwszWordWheel;
  715. m_dwTitleId = dwTitleId;
  716. }
  717. CWordWheel::CWordWheel( CTitleDatabase* pDatabase, const CHAR* pszWordWheel, DWORD dwTitleId )
  718. {
  719. _CWordWheel();
  720. m_pDatabase = pDatabase;
  721. m_pszWordWheelIn = pszWordWheel;
  722. m_dwTitleId = dwTitleId;
  723. }
  724. CWordWheel::~CWordWheel()
  725. {
  726. Free();
  727. }
  728. DWORD CWordWheel::AddRef()
  729. {
  730. return ++m_dwRefCount;
  731. }
  732. DWORD CWordWheel::Release()
  733. {
  734. if( m_dwRefCount )
  735. --m_dwRefCount;
  736. return m_dwRefCount;
  737. }
  738. BOOL CWordWheel::Initialize()
  739. {
  740. // bail out if we are already initialized
  741. if( m_bInit )
  742. return TRUE;
  743. // allocate a UNICODE version of our word wheel name
  744. if( m_pszWordWheelIn && *m_pszWordWheelIn ) {
  745. DWORD dwLen = (DWORD)strlen(m_pszWordWheelIn) + 1;
  746. m_pwszWordWheel = new WCHAR[dwLen+1];
  747. MultiByteToWideChar(CP_ACP, 0, m_pszWordWheelIn, -1, (WCHAR*) m_pwszWordWheel, dwLen);
  748. }
  749. else if( m_pwszWordWheelIn && *m_pwszWordWheelIn ) {
  750. DWORD dwLen = wcslen(m_pwszWordWheelIn) + 1;
  751. m_pwszWordWheel = new WCHAR[dwLen];
  752. wcscpy( (WCHAR*) m_pwszWordWheel, m_pwszWordWheelIn );
  753. }
  754. // bail out if word wheel name not specified
  755. if( !m_pwszWordWheel || !*m_pwszWordWheel )
  756. return FALSE;
  757. BOOL bReturn = FALSE;
  758. HRESULT hr = S_OK;
  759. // get ITWordWheel ptr.
  760. if( SUCCEEDED(hr = CoCreateInstance(CLSID_IITWordWheelLocal, NULL, CLSCTX_INPROC_SERVER,
  761. IID_IITWordWheel, (void**)&m_pWordWheel) ) ) {
  762. // open the word wheel
  763. if( SUCCEEDED(hr = m_pWordWheel->Open( m_pDatabase->GetDatabase(), m_pwszWordWheel, ITWW_OPEN_NOCONNECT) ) ) {
  764. bReturn = TRUE;
  765. }
  766. }
  767. if( !bReturn ) {
  768. Free();
  769. m_bInit = FALSE;
  770. }
  771. else
  772. m_bInit = TRUE;
  773. return bReturn;
  774. }
  775. BOOL CWordWheel::Free()
  776. {
  777. BOOL bReturn = FALSE;
  778. if( m_pWordWheel ) {
  779. m_pWordWheel->Close();
  780. m_pWordWheel->Release();
  781. m_pWordWheel = NULL;
  782. }
  783. if( m_pwszWordWheel ) {
  784. delete [] (WCHAR*) m_pwszWordWheel;
  785. m_pwszWordWheel = NULL;
  786. }
  787. if( m_pwszWordWheelIn )
  788. m_pwszWordWheelIn = NULL;
  789. if( m_pszWordWheelIn )
  790. m_pszWordWheelIn = NULL;
  791. m_bInit = FALSE;
  792. bReturn = TRUE;
  793. return bReturn;
  794. }
  795. DWORD CWordWheel::GetCount()
  796. {
  797. DWORD dwReturn = HHWW_ERROR;
  798. if( Init() ) {
  799. if( m_dwCount == HHWW_ERROR ) {
  800. LONG nCount;
  801. if( SUCCEEDED(m_pWordWheel->Count(&nCount)) )
  802. dwReturn = m_dwCount = nCount;
  803. }
  804. else
  805. dwReturn = m_dwCount;
  806. }
  807. return dwReturn;
  808. }
  809. /////////////////////////////////////////////////////////////////////////////
  810. // CWordWheel::GetIndex
  811. //
  812. // params:
  813. //
  814. // pwszKeywordIn - keyword to lookup
  815. //
  816. // bFragment - set this to TRUE if only looking for a partial match
  817. // such as when the user types in a string fragment
  818. // in the Index tab
  819. //
  820. // pdwIndexLast - If not NULL, then the function should return the
  821. // first equivalent index and set the contents of this
  822. // argument to the index of the last equivalent keyword.
  823. // And equivalent keyword is where the root word (less
  824. // and special prefixes) is the same regardless of case.
  825. // For example, "_open", "open", "Open", and "OPEN" are
  826. // equivalent keywords. This should be used for F1 lookups
  827. // and A/KLink lookups as well.
  828. //
  829. // Note: if bFragment is TRUE and pdwIndexLast is non-NULL then
  830. // bFragment will be set to FALSE. If neither, then we look for an
  831. // exact match only.
  832. //
  833. DWORD CWordWheel::GetIndex( const WCHAR* pwszKeywordIn, BOOL bFragment, DWORD* pdwIndexLast )
  834. {
  835. DWORD dwIndexFirst = HHWW_ERROR;
  836. // we cannot do both a fragment lookup and an equivalent lookup
  837. if( pdwIndexLast )
  838. bFragment = FALSE;
  839. if( Init() && pwszKeywordIn && *pwszKeywordIn ) {
  840. if( GetCount() == (DWORD) -1 )
  841. return dwIndexFirst;
  842. // since we are using a pluggable sort object the input to Lookup must
  843. // be in the save format as the sort object itself. That is, we need
  844. // to add an HHKEYINFO structure to the end of this string and fill in
  845. // the data properly since the CHHSysSort::GetSize function in our
  846. // pluggable sort module will get this object and expect in in that format
  847. //
  848. // Note, if it did not contain the trailing struct it can and will fault.
  849. BYTE KeywordObject[HHWW_MAX_KEYWORD_OBJECT_SIZE];
  850. WCHAR* pwszKeywordObject = (WCHAR*) KeywordObject;
  851. wcsncpy( pwszKeywordObject, pwszKeywordIn, HHWW_MAX_KEYWORD_LENGTH );
  852. pwszKeywordObject[HHWW_MAX_KEYWORD_LENGTH] = 0;
  853. #ifdef _DEBUG
  854. int iMaxObject = HHWW_MAX_KEYWORD_OBJECT_SIZE;
  855. int iMaxLen = HHWW_MAX_KEYWORD_LENGTH;
  856. int iLen = wcslen(pwszKeywordObject);
  857. #endif
  858. HHKEYINFO Info;
  859. Info.wFlags = 0;
  860. Info.wLevel = 0;
  861. Info.dwLevelOffset = 0;
  862. Info.dwFont = 0;
  863. Info.dwCount = 0;
  864. DWORD dwLength = sizeof(WCHAR) * (wcslen(pwszKeywordObject) + 1);
  865. *((HHKEYINFO*)(((DWORD_PTR)pwszKeywordObject)+dwLength)) = Info;
  866. // There are three kinds of lookup matches:
  867. //
  868. // 1. Exact - found hits must be completely indentical to the
  869. // keyword we are looking up.
  870. // "FOOBAR" == "FOOBAR"
  871. //
  872. // 2. Equivalent - the keyword must only differ by case or prefixes
  873. // and we continue to find all keywords, not just the
  874. // first one, that meets this criteria.
  875. // "_foobar" == "FooBar" == "FOOBAR"
  876. //
  877. // 3. Fragment - find the first hit where only the first non-prefixed
  878. // characters need to match while ignoring case.
  879. // "~fo" == "FOOBAR" (assuming nothing else was a closer match)
  880. // We have three ways that we perform such lookups:
  881. //
  882. // 1. Index tab - Tries Exact first then uses Fragment lookups.
  883. //
  884. // 2. F1 Lookups - Tries Exact first then uses Equivalent lookups.
  885. //
  886. // 3. A/Klinks - Tries Exact first then uses Equivalent lookups.
  887. // Centaur just doesn't do the right thing for non-exact,
  888. // a.k.a. prefix, lookups!
  889. //
  890. // For exact matches, try the first found keyword and verify it.
  891. // If it does not match, then try a fragment lookup.
  892. //
  893. // For fragment matches, we need to first try the exact match
  894. // technique noted above.
  895. // If it does not match, then try a partial (size of lookup word),
  896. // case-insensitive lookup.
  897. // If it does not match, and then try the *next* entry in the same fashion.
  898. BYTE KeywordObjectTry[HHWW_MAX_KEYWORD_OBJECT_SIZE];
  899. WCHAR* pwszKeywordObjectTry = (WCHAR*) KeywordObjectTry;
  900. // Try exact matches first
  901. if( SUCCEEDED(m_pWordWheel->Lookup( &KeywordObject, (BOOL) TRUE, (LONG*) &dwIndexFirst )) ) {
  902. if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexFirst, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) {
  903. if( !(wcscmp( pwszKeywordObjectTry, pwszKeywordObject ) == 0) ) {
  904. dwIndexFirst = HHWW_ERROR;
  905. }
  906. }
  907. }
  908. // Try equivalent match next
  909. if( pdwIndexLast && (dwIndexFirst == HHWW_ERROR) ) {
  910. // skip over any special chars
  911. const WCHAR* pwszKeyword = NULL;
  912. for( pwszKeyword = pwszKeywordObject; pwszKeyword; pwszKeyword++ ) {
  913. if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) )
  914. break;
  915. }
  916. // Try a prefix lookup
  917. if( SUCCEEDED(m_pWordWheel->Lookup( pwszKeyword, (BOOL) FALSE, (LONG*) &dwIndexFirst )) ) {
  918. if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexFirst, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) {
  919. // validate the hit, if it fails, try the next index value
  920. DWORD dwTryFirst = dwIndexFirst;
  921. dwIndexFirst = HHWW_ERROR;
  922. for( DWORD dwTry = dwTryFirst; dwTry <= dwTryFirst+1 ; dwTry++ ) {
  923. if( SUCCEEDED(m_pWordWheel->Lookup( dwTry, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) {
  924. // try it without prefixes in the input string
  925. if( wcsicmp( pwszKeywordObjectTry, pwszKeyword ) == 0 ) {
  926. dwIndexFirst = dwTry;
  927. break;
  928. } // try it with the prefixes back in
  929. else if( ((DWORD_PTR) pwszKeyword) != ((DWORD_PTR) pwszKeywordObject) ) {
  930. if( wcsicmp( pwszKeywordObjectTry, pwszKeywordObject ) == 0 ) {
  931. dwIndexFirst = dwTry;
  932. break;
  933. }
  934. } // ignore prefixes in found hit
  935. else if( (*pwszKeywordObjectTry == L'_') || (*pwszKeywordObjectTry == L'~') ) {
  936. const WCHAR* pwszKeyword = NULL;
  937. for( pwszKeyword = pwszKeywordObjectTry; pwszKeyword; pwszKeyword++ ) {
  938. if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) )
  939. break;
  940. }
  941. if( wcsicmp( pwszKeywordObject, pwszKeyword ) == 0 ) {
  942. dwIndexFirst = dwTry;
  943. break;
  944. }
  945. }
  946. }
  947. }
  948. }
  949. }
  950. }
  951. // Try fragment match last
  952. if( bFragment && (dwIndexFirst == HHWW_ERROR) ) {
  953. // skip over any special chars
  954. const WCHAR* pwszKeyword = NULL;
  955. for( pwszKeyword = pwszKeywordObject; pwszKeyword; pwszKeyword++ ) {
  956. if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) )
  957. break;
  958. }
  959. while( TRUE ) {
  960. if( SUCCEEDED(m_pWordWheel->Lookup( pwszKeyword, (BOOL) FALSE, (LONG*) &dwIndexFirst )) ) {
  961. int iLen = wcslen( pwszKeyword );
  962. if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexFirst, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) {
  963. // validate the hit, if it fails, try the next index value
  964. DWORD dwTryFirst = dwIndexFirst;
  965. dwIndexFirst = HHWW_ERROR;
  966. for( DWORD dwTry = dwTryFirst; dwTry <= dwTryFirst+1; dwTry++ ) {
  967. if( SUCCEEDED(m_pWordWheel->Lookup( dwTry, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) {
  968. // try it without prefixes in the input string
  969. int iLen2 = min(iLen, (int) wcslen(pwszKeywordObjectTry));
  970. if( wcsnicmp( pwszKeywordObjectTry, pwszKeyword, iLen2 ) == 0 ) {
  971. dwIndexFirst = dwTry;
  972. break;
  973. } // try it with the prefixes back in
  974. else if( ((DWORD_PTR) pwszKeyword) != ((DWORD_PTR) pwszKeywordObject) ) {
  975. int iLen = wcslen( pwszKeywordObject );
  976. if( wcsnicmp( pwszKeywordObjectTry, pwszKeywordObject, iLen2 ) == 0 ) {
  977. dwIndexFirst = dwTry;
  978. break;
  979. }
  980. } // ignore prefixes in found hit
  981. else if( (*pwszKeywordObjectTry == L'_') || (*pwszKeywordObjectTry == L'~') ) {
  982. const WCHAR* pwszKeyword = NULL;
  983. for( pwszKeyword = pwszKeywordObjectTry; pwszKeyword; pwszKeyword++ ) {
  984. if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) )
  985. break;
  986. }
  987. if( wcsnicmp( pwszKeywordObject, pwszKeyword, iLen2 ) == 0 ) {
  988. dwIndexFirst = dwTry;
  989. break;
  990. }
  991. } // for framgent lookups only, when all else fails,
  992. // simply trust the first try (works half the time)
  993. else if( dwTry == dwTryFirst+1 ) {
  994. dwIndexFirst = dwTryFirst;
  995. break;
  996. }
  997. }
  998. }
  999. }
  1000. }
  1001. // for fragments, we need to keep trying until we get a hit by
  1002. // trimming the trailing chars one at a time until we get a match
  1003. if( KeywordObject[0] && (dwIndexFirst == HHWW_ERROR) ) {
  1004. DWORD dwLength = wcslen(pwszKeywordObject);
  1005. if( dwLength <= 1 )
  1006. break;
  1007. pwszKeywordObject[dwLength-1] = L'\0';
  1008. *((HHKEYINFO*)(((DWORD_PTR)KeywordObject)+(dwLength*sizeof(WCHAR)))) = Info;
  1009. continue;
  1010. }
  1011. break;
  1012. }
  1013. }
  1014. // if equivalent match found then find the real first and last equivalent
  1015. if( pdwIndexLast && (dwIndexFirst != HHWW_ERROR) ) {
  1016. DWORD dwIndexFirstTry = dwIndexFirst;
  1017. DWORD dwIndexLastTry = dwIndexFirst;
  1018. // skip over any special chars
  1019. const WCHAR* pwszKeyword = NULL;
  1020. for( pwszKeyword = pwszKeywordObject; pwszKeyword; pwszKeyword++ ) {
  1021. if( !(( (*pwszKeyword) == L'_') || ( (*pwszKeyword) == L'~')) )
  1022. break;
  1023. }
  1024. // find the first one
  1025. for( dwIndexFirstTry--; dwIndexFirstTry != (DWORD)-1; dwIndexFirstTry-- ) {
  1026. if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexFirstTry, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) {
  1027. const WCHAR* pwszBuffer = NULL;
  1028. for( pwszBuffer = pwszKeywordObjectTry; pwszBuffer; pwszBuffer++ ) {
  1029. if( !(( (*pwszBuffer) == L'_') || ( (*pwszBuffer) == L'~')) )
  1030. break;
  1031. }
  1032. if( !(_wcsicmp( pwszBuffer, pwszKeyword ) == 0) )
  1033. break;
  1034. dwIndexFirst = dwIndexFirstTry;
  1035. }
  1036. }
  1037. // find the last one
  1038. *pdwIndexLast = dwIndexLastTry;
  1039. for( dwIndexLastTry++; dwIndexLastTry <= m_dwCount; dwIndexLastTry++ ) {
  1040. if( SUCCEEDED(m_pWordWheel->Lookup( dwIndexLastTry, &KeywordObjectTry, sizeof(KeywordObjectTry) )) ) {
  1041. const WCHAR* pwszBuffer = NULL;
  1042. for( pwszBuffer = pwszKeywordObjectTry; pwszBuffer; pwszBuffer++ ) {
  1043. if( !(( (*pwszBuffer) == L'_') || ( (*pwszBuffer) == L'~')) )
  1044. break;
  1045. }
  1046. if( !(_wcsicmp( pwszBuffer, pwszKeyword ) == 0) )
  1047. break;
  1048. *pdwIndexLast = dwIndexLastTry;
  1049. }
  1050. }
  1051. }
  1052. }
  1053. return dwIndexFirst;
  1054. }
  1055. DWORD CWordWheel::GetIndex( const CHAR* pszKeyword, BOOL bFragment, DWORD* pdwIndexLast )
  1056. {
  1057. DWORD dwReturn = HHWW_ERROR;
  1058. if( pszKeyword && *pszKeyword ) {
  1059. WCHAR wszKeyword[HHWW_MAX_KEYWORD_LENGTH+1];
  1060. if( MultiByteToWideChar(CP_ACP, 0, pszKeyword, -1, wszKeyword, HHWW_MAX_KEYWORD_LENGTH+1) )
  1061. dwReturn = GetIndex( wszKeyword, bFragment, pdwIndexLast );
  1062. }
  1063. return dwReturn;
  1064. }
  1065. BOOL CWordWheel::GetString( DWORD dwKeyword, WCHAR* pwszBuffer, DWORD cchBuffer, BOOL bFull, BOOL bCacheAll )
  1066. {
  1067. BOOL bReturn = FALSE;
  1068. if( pwszBuffer && cchBuffer>0 ) {
  1069. if( (bReturn = GetIndexData( dwKeyword, bCacheAll ) ) ) {
  1070. DWORD dwLength = 0;
  1071. if( bFull )
  1072. dwLength = wcslen(m_CachedEntry.m_wszFullKeyword)+1;
  1073. else
  1074. dwLength = wcslen(m_CachedEntry.m_wszKeyword)+1;
  1075. if( dwLength <= cchBuffer )
  1076. if( bFull )
  1077. wcscpy( pwszBuffer, m_CachedEntry.m_wszFullKeyword );
  1078. else
  1079. wcscpy( pwszBuffer, m_CachedEntry.m_wszKeyword );
  1080. else
  1081. *pwszBuffer = L'\0';
  1082. }
  1083. }
  1084. return bReturn;
  1085. }
  1086. BOOL CWordWheel::GetString( DWORD dwKeyword, CHAR* pszBuffer, DWORD cchBuffer, BOOL bFull, BOOL bCacheAll )
  1087. {
  1088. BOOL bReturn = FALSE;
  1089. if( pszBuffer && cchBuffer>0 ) {
  1090. WCHAR wszBuffer[HHWW_MAX_KEYWORD_LENGTH+1];
  1091. if( bReturn = GetString( dwKeyword, wszBuffer, cchBuffer, bFull, bCacheAll ) ) {
  1092. if( WideCharToMultiByte(CP_ACP, 0, wszBuffer, -1, pszBuffer, cchBuffer, NULL, NULL) == 0 )
  1093. bReturn = TRUE;
  1094. }
  1095. }
  1096. return bReturn;
  1097. }
  1098. DWORD CWordWheel::GetLevel( DWORD dwKeyword )
  1099. {
  1100. DWORD dwReturn = HHWW_ERROR;
  1101. if( GetIndexData( dwKeyword ) )
  1102. dwReturn = m_CachedEntry.m_dwLevel;
  1103. return dwReturn;
  1104. }
  1105. DWORD CWordWheel::GetLevelOffset( DWORD dwKeyword )
  1106. {
  1107. DWORD dwReturn = HHWW_ERROR;
  1108. if( GetIndexData( dwKeyword ) )
  1109. dwReturn = m_CachedEntry.m_dwLevelOffset;
  1110. return dwReturn;
  1111. }
  1112. BOOL CWordWheel::IsPlaceHolder( DWORD dwKeyword )
  1113. {
  1114. BOOL bReturn = FALSE;
  1115. if( GetIndexData( dwKeyword ) ) {
  1116. if( m_CachedEntry.m_dwFlags & HHWW_SEEALSO ) {
  1117. if( wcscmp( m_CachedEntry.m_wszSeeAlso, m_CachedEntry.m_wszFullKeyword ) == 0 ) {
  1118. bReturn = TRUE;
  1119. }
  1120. }
  1121. }
  1122. return bReturn;
  1123. }
  1124. BOOL CWordWheel::GetSeeAlso( DWORD dwKeyword, WCHAR* pwszBuffer, DWORD cchBuffer )
  1125. {
  1126. BOOL bReturn = FALSE;
  1127. if( pwszBuffer && cchBuffer>0 ) {
  1128. if( (bReturn = GetIndexData( dwKeyword ) ) ) {
  1129. if( m_CachedEntry.m_dwFlags & HHWW_SEEALSO ) {
  1130. if( (DWORD) (wcslen(m_CachedEntry.m_wszSeeAlso)+1) <= cchBuffer )
  1131. wcscpy( pwszBuffer, m_CachedEntry.m_wszSeeAlso );
  1132. else
  1133. *pwszBuffer = L'\0';
  1134. }
  1135. else
  1136. bReturn = FALSE;
  1137. }
  1138. }
  1139. return bReturn;
  1140. }
  1141. BOOL CWordWheel::GetSeeAlso( DWORD dwKeyword, CHAR* pszBuffer, DWORD cchBuffer )
  1142. {
  1143. BOOL bReturn = FALSE;
  1144. if( pszBuffer && cchBuffer>0 ) {
  1145. WCHAR wszBuffer[HHWW_MAX_KEYWORD_LENGTH+1];
  1146. if( bReturn = GetSeeAlso( dwKeyword, wszBuffer, cchBuffer ) ) {
  1147. if( WideCharToMultiByte(CP_ACP, 0, wszBuffer, -1, pszBuffer, cchBuffer, NULL, NULL) == 0 )
  1148. bReturn = TRUE;
  1149. }
  1150. }
  1151. return bReturn;
  1152. }
  1153. DWORD CWordWheel::GetHitCount( DWORD dwKeyword )
  1154. {
  1155. DWORD dwReturn = HHWW_ERROR;
  1156. if( GetIndexHitData( dwKeyword ) )
  1157. dwReturn = m_CachedResults.GetCount();
  1158. return dwReturn;
  1159. }
  1160. DWORD CWordWheel::GetHit( DWORD dwKeyword, DWORD dwHit, CExTitle** ppTitle )
  1161. {
  1162. DWORD dwReturn = HHWW_ERROR;
  1163. if( GetIndexHitData( dwKeyword ) ) {
  1164. dwReturn = m_CachedResults.GetAt( dwHit )->GetURLId();
  1165. if( ppTitle != NULL )
  1166. *ppTitle = m_CachedResults.GetAt( dwHit )->GetTitle();
  1167. }
  1168. return dwReturn;
  1169. }
  1170. inline BOOL CWordWheel::GetIndexHitData( const VOID* pcvKeywordObject, DWORD cbSize, HHKEYINFO* pInfo, DWORD dwKeyword )
  1171. {
  1172. if( !((pInfo->wFlags) & HHWW_UID_OVERFLOW) && pInfo->dwCount ) {
  1173. m_CachedResults.SetIndex( dwKeyword, pInfo->dwCount );
  1174. for( int i = 0; i < (int) pInfo->dwCount; i++ ) {
  1175. DWORD dwURLId = *((UNALIGNED DWORD*) (((DWORD_PTR)pcvKeywordObject) + cbSize + (i*sizeof(DWORD))) );
  1176. CExTitle* pTitle = m_pDatabase->GetTitle();
  1177. // if we are reading a word wheel that is to be merged then
  1178. // translate the URL Ids into 12/20 format
  1179. if( m_dwTitleId ) {
  1180. dwURLId = (m_dwTitleId<<20) + dwURLId;
  1181. }
  1182. // if we are reading a collection file then
  1183. // translate the URL Ids to standard format
  1184. // and set the title pointer appropriately
  1185. else if( m_pDatabase->IsCollection() ) {
  1186. DWORD dwTitleId = dwURLId>>20;
  1187. // v1.1a creates bogus entries in the chw file. So
  1188. // to workaround this we need to set the title id to 1
  1189. // and the url id to 0 when the title id is greater than
  1190. // the title count
  1191. if( dwTitleId > m_pDatabase->GetTitleMap()->GetCount() ) {
  1192. dwTitleId = 1;
  1193. dwURLId = 0;
  1194. }
  1195. else if( dwTitleId ) {
  1196. dwURLId = dwURLId & 0x000FFFFF;
  1197. pTitle = m_pDatabase->GetTitleMap()->GetAt(dwTitleId-1)->GetTitle();
  1198. }
  1199. }
  1200. m_CachedResults.GetAt( i )->SetURLId( dwURLId );
  1201. m_CachedResults.GetAt( i )->SetTitle( pTitle );
  1202. }
  1203. }
  1204. return TRUE;
  1205. }
  1206. BOOL CWordWheel::GetIndexData( DWORD dwKeyword, BOOL bCacheAll )
  1207. {
  1208. BOOL bReturn = FALSE;
  1209. if( Init() ) {
  1210. if( m_CachedEntry.m_dwIndex != dwKeyword ) {
  1211. BYTE KeywordObject[HHWW_MAX_KEYWORD_OBJECT_SIZE];
  1212. const VOID* pcvKeywordObject = KeywordObject;
  1213. if( SUCCEEDED(m_pWordWheel->Lookup( dwKeyword, KeywordObject, sizeof(KeywordObject) ) ) ) {
  1214. m_CachedEntry.m_dwIndex = dwKeyword;
  1215. wcscpy( m_CachedEntry.m_wszFullKeyword, (WCHAR*) pcvKeywordObject );
  1216. DWORD cbSize = sizeof(WCHAR) * (wcslen((WCHAR*)pcvKeywordObject) + 1);
  1217. HHKEYINFO* pInfo = (HHKEYINFO*)(((DWORD_PTR)(pcvKeywordObject))+cbSize);
  1218. m_CachedEntry.m_dwFlags = (DWORD) pInfo->wFlags;
  1219. m_CachedEntry.m_dwLevel = (DWORD) pInfo->wLevel;
  1220. m_CachedEntry.m_dwLevelOffset = pInfo->dwLevelOffset;
  1221. cbSize += sizeof(HHKEYINFO);
  1222. if( pInfo->wLevel )
  1223. wcscpy( m_CachedEntry.m_wszKeyword, (WCHAR*) (((DWORD_PTR)pcvKeywordObject)+(pInfo->dwLevelOffset*sizeof(WCHAR))) );
  1224. else
  1225. wcscpy( m_CachedEntry.m_wszKeyword, (WCHAR*) pcvKeywordObject );
  1226. if( (pInfo->wFlags) & HHWW_SEEALSO ) {
  1227. wcscpy( m_CachedEntry.m_wszSeeAlso, (WCHAR*)(((DWORD_PTR)pcvKeywordObject)+cbSize) );
  1228. }
  1229. if( bCacheAll ) {
  1230. GetIndexHitData( pcvKeywordObject, cbSize, pInfo, dwKeyword );
  1231. }
  1232. }
  1233. }
  1234. if( dwKeyword < GetCount() )
  1235. bReturn = TRUE;
  1236. }
  1237. return bReturn;
  1238. }
  1239. BOOL CWordWheel::GetIndexHitData( DWORD dwKeyword )
  1240. {
  1241. BOOL bReturn = FALSE;
  1242. if( Init() ) {
  1243. if( m_CachedResults.GetIndex() != dwKeyword ) {
  1244. BYTE KeywordObject[HHWW_MAX_KEYWORD_OBJECT_SIZE];
  1245. const VOID* pcvKeywordObject = KeywordObject;
  1246. if( SUCCEEDED(m_pWordWheel->Lookup( dwKeyword, KeywordObject, sizeof(KeywordObject) ) ) ) {
  1247. DWORD cbSize = sizeof(WCHAR) * (wcslen((WCHAR*)pcvKeywordObject) + 1);
  1248. HHKEYINFO* pInfo = (HHKEYINFO*)(((DWORD_PTR)(pcvKeywordObject))+cbSize);
  1249. cbSize += sizeof(HHKEYINFO);
  1250. GetIndexHitData( pcvKeywordObject, cbSize, pInfo, dwKeyword );
  1251. }
  1252. }
  1253. if( dwKeyword < GetCount() )
  1254. bReturn = TRUE;
  1255. }
  1256. return bReturn;
  1257. }
  1258. /////////////////////////////////////////////////////////////////////////////
  1259. // class CWordWheelCompiler implementation
  1260. void CWordWheelCompiler::_CWordWheelCompiler()
  1261. {
  1262. m_bInit = FALSE;
  1263. m_pszDatabase = NULL;
  1264. m_pwszKeywordLinks = NULL;
  1265. m_pwszAssociativeLinks = NULL;
  1266. m_lcid = 0;
  1267. m_pFileSystem = NULL;
  1268. m_pDatabase = NULL;
  1269. m_pPersistStorageDatabase = NULL;
  1270. m_pBuildCollectKeywordLinks = NULL;
  1271. m_pBuildCollectAssociativeLinks = NULL;
  1272. m_pPropList = NULL;
  1273. m_pStorageKeywordLinks = NULL;
  1274. m_pStorageAssociativeLinks = NULL;
  1275. m_pPersistStorageKeywordLinks = NULL;
  1276. m_pPersistStorageAssociativeLinks = NULL;
  1277. }
  1278. CWordWheelCompiler::CWordWheelCompiler( const CHAR* pszDatabase, const WCHAR* pwszKeywordLinks, const WCHAR* pwszAssociativeLinks, LCID lcid )
  1279. {
  1280. _CWordWheelCompiler();
  1281. m_pszDatabase = pszDatabase;
  1282. m_pwszKeywordLinks = pwszKeywordLinks;
  1283. m_pwszAssociativeLinks = pwszAssociativeLinks;
  1284. m_lcid = lcid;
  1285. }
  1286. CWordWheelCompiler::~CWordWheelCompiler()
  1287. {
  1288. Free();
  1289. }
  1290. HRESULT CWordWheelCompiler::Initialize()
  1291. {
  1292. HRESULT hr = S_FALSE;
  1293. // bail out if we are already initialized
  1294. if( m_bInit )
  1295. return S_OK;
  1296. // bail out if word wheel stream names not specified
  1297. if( !m_pwszKeywordLinks || !*m_pwszKeywordLinks ||
  1298. !m_pwszAssociativeLinks || !*m_pwszAssociativeLinks )
  1299. return S_FALSE;
  1300. // if the database is specified then use it otherwise generate
  1301. // a temporary filename
  1302. char szTempPath[MAX_PATH];
  1303. GetTempPath( sizeof(szTempPath), szTempPath );
  1304. if( m_pszDatabase && *m_pszDatabase ) {
  1305. strcpy( m_szDatabase, m_pszDatabase );
  1306. }
  1307. else {
  1308. GetTempFileName( szTempPath,"TFS",0, m_szDatabase );
  1309. }
  1310. // create the file system (delete the old one if it exists
  1311. if( IsFile( m_szDatabase ) )
  1312. DeleteFile( m_szDatabase );
  1313. m_pFileSystem = new CFileSystem;
  1314. if( !m_pFileSystem || FAILED( m_pFileSystem->Init() ) || FAILED( m_pFileSystem->CreateUncompressed( m_szDatabase ) ) ) {
  1315. return S_FALSE;
  1316. }
  1317. // get ITDatabase ptr.
  1318. if( SUCCEEDED(hr = CoCreateInstance(CLSID_IITDatabaseLocal, NULL, CLSCTX_INPROC_SERVER,
  1319. IID_IITDatabase, (void**)&m_pDatabase) ) ) {
  1320. if( SUCCEEDED(hr = m_pDatabase->QueryInterface( IID_IPersistStorage, (void**)&m_pPersistStorageDatabase ) ) ) {
  1321. if( SUCCEEDED(hr = m_pPersistStorageDatabase->InitNew( m_pFileSystem->GetITStorageDocObj() ) ) ) {
  1322. m_bInit = TRUE;
  1323. }
  1324. }
  1325. }
  1326. // create the sorter object
  1327. DWORD dwSorterInstance;
  1328. m_pDatabase->CreateObject( CLSID_HHSysSort, &dwSorterInstance );
  1329. // Create Build Collection objects
  1330. if( SUCCEEDED(hr = CoCreateInstance( CLSID_IITWordWheelUpdate, NULL, CLSCTX_INPROC_SERVER,
  1331. IID_IITBuildCollect, (VOID**)&m_pBuildCollectKeywordLinks ) ) ) {
  1332. if( SUCCEEDED(hr = CoCreateInstance( CLSID_IITWordWheelUpdate, NULL, CLSCTX_INPROC_SERVER,
  1333. IID_IITBuildCollect, (VOID**)&m_pBuildCollectAssociativeLinks ) ) ) {
  1334. // Create keyword Property List (used for both AssociativeLinks and KeywordLinks)
  1335. if( SUCCEEDED(hr = CoCreateInstance( CLSID_IITPropList, NULL, CLSCTX_INPROC_SERVER, IID_IITPropList, (VOID**)&m_pPropList) ) ) {
  1336. m_bInit = TRUE;
  1337. }
  1338. }
  1339. }
  1340. // create the substorage files
  1341. DWORD dwLen = 0;
  1342. CHAR* psz = NULL;
  1343. // KeywordLinks
  1344. WCHAR wszKeywordLinks[MAX_PATH];
  1345. m_pBuildCollectKeywordLinks->GetTypeString( wszKeywordLinks, NULL ); // Get the "$WW" prefix
  1346. wcscat( wszKeywordLinks, m_pwszKeywordLinks );
  1347. m_pFileSystem->GetITStorageDocObj()->CreateStorage( wszKeywordLinks, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &m_pStorageKeywordLinks);
  1348. m_pBuildCollectKeywordLinks->QueryInterface( IID_IPersistStorage, (void**)&m_pPersistStorageKeywordLinks );
  1349. m_pPersistStorageKeywordLinks->InitNew( m_pStorageKeywordLinks );
  1350. // AssociativeLinks
  1351. WCHAR wszAssociativeLinks[MAX_PATH];
  1352. m_pBuildCollectAssociativeLinks->GetTypeString( wszAssociativeLinks, NULL ); // Get the "$WW" prefix
  1353. wcscat( wszAssociativeLinks, m_pwszAssociativeLinks );
  1354. m_pFileSystem->GetITStorageDocObj()->CreateStorage( wszAssociativeLinks, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &m_pStorageAssociativeLinks);
  1355. m_pBuildCollectAssociativeLinks->QueryInterface( IID_IPersistStorage, (void**)&m_pPersistStorageAssociativeLinks );
  1356. m_pPersistStorageAssociativeLinks->InitNew( m_pStorageAssociativeLinks );
  1357. // get local information
  1358. char szCodePage[20] = "1252";
  1359. if( m_lcid == ((DWORD)-1) )
  1360. m_lcid = GetSystemDefaultLCID();
  1361. GetLocaleInfo(m_lcid,LOCALE_IDEFAULTANSICODEPAGE,szCodePage,sizeof(szCodePage) );
  1362. DWORD dwCodePage = Atoi( szCodePage );
  1363. // apply sorter object information to the new word wheels
  1364. VARARG vaDword = {0};
  1365. vaDword.dwArgc = 2;
  1366. vaDword.Argv[0] = (void *)(INT_PTR)IHHSK100_KEYTYPE_UNICODE_SZ;
  1367. vaDword.Argv[1] = (void*) 0;
  1368. VARARG vaEmpty = {0};
  1369. m_pBuildCollectKeywordLinks->InitHelperInstance( dwSorterInstance, m_pDatabase,
  1370. dwCodePage, m_lcid, vaDword, vaEmpty );
  1371. m_pBuildCollectAssociativeLinks->InitHelperInstance( dwSorterInstance, m_pDatabase,
  1372. dwCodePage, m_lcid, vaDword, vaEmpty );
  1373. if( FAILED(hr) ) {
  1374. Free();
  1375. m_bInit = FALSE;
  1376. }
  1377. else
  1378. m_bInit = TRUE;
  1379. return hr;
  1380. }
  1381. HRESULT CWordWheelCompiler::Free()
  1382. {
  1383. HRESULT hr = S_FALSE;
  1384. if( m_pPersistStorageKeywordLinks ) {
  1385. m_pPersistStorageKeywordLinks->Release();
  1386. m_pPersistStorageKeywordLinks = NULL;
  1387. }
  1388. if( m_pPersistStorageAssociativeLinks ) {
  1389. m_pPersistStorageAssociativeLinks->Release();
  1390. m_pPersistStorageAssociativeLinks = NULL;
  1391. }
  1392. if( m_pStorageKeywordLinks ) {
  1393. m_pStorageKeywordLinks->Release();
  1394. m_pStorageKeywordLinks = NULL;
  1395. }
  1396. if( m_pStorageAssociativeLinks ) {
  1397. m_pStorageAssociativeLinks->Release();
  1398. m_pStorageAssociativeLinks = NULL;
  1399. }
  1400. if( m_pPropList ) {
  1401. m_pPropList->Release();
  1402. m_pPropList = NULL;
  1403. }
  1404. if( m_pBuildCollectAssociativeLinks ) {
  1405. m_pBuildCollectAssociativeLinks->Release();
  1406. m_pBuildCollectAssociativeLinks = NULL;
  1407. }
  1408. if( m_pBuildCollectKeywordLinks ) {
  1409. m_pBuildCollectKeywordLinks->Release();
  1410. m_pBuildCollectKeywordLinks = NULL;
  1411. }
  1412. if( m_pPersistStorageDatabase ) {
  1413. m_pPersistStorageDatabase->Release();
  1414. m_pPersistStorageDatabase = NULL;
  1415. }
  1416. if( m_pDatabase ) {
  1417. m_pDatabase->Close();
  1418. m_pDatabase->Release();
  1419. m_pDatabase = NULL;
  1420. }
  1421. if( m_pFileSystem ) {
  1422. delete m_pFileSystem;
  1423. m_pFileSystem = NULL;
  1424. }
  1425. m_bInit = FALSE;
  1426. hr = S_OK;
  1427. return hr;
  1428. }
  1429. HRESULT CWordWheelCompiler::Build()
  1430. {
  1431. HRESULT hr = S_FALSE;
  1432. if( Init() ) {
  1433. #ifdef CHIINDEX
  1434. if ( m_bAnimation )
  1435. #endif
  1436. NextAnimation();
  1437. // KeywordLinks
  1438. m_pPersistStorageKeywordLinks->Save( m_pStorageKeywordLinks, TRUE );
  1439. #ifdef CHIINDEX
  1440. if ( m_bAnimation )
  1441. #endif
  1442. NextAnimation();
  1443. m_pPersistStorageKeywordLinks->Release();
  1444. m_pPersistStorageKeywordLinks = NULL;
  1445. #ifdef CHIINDEX
  1446. if ( m_bAnimation )
  1447. #endif
  1448. NextAnimation();
  1449. m_pStorageKeywordLinks->Commit(STGC_DEFAULT);
  1450. #ifdef CHIINDEX
  1451. if ( m_bAnimation )
  1452. #endif
  1453. NextAnimation();
  1454. // AssociativeLinks
  1455. m_pPersistStorageAssociativeLinks->Save( m_pStorageAssociativeLinks, TRUE );
  1456. #ifdef CHIINDEX
  1457. if ( m_bAnimation )
  1458. #endif
  1459. NextAnimation();
  1460. m_pPersistStorageAssociativeLinks->Release();
  1461. m_pPersistStorageAssociativeLinks = NULL;
  1462. #ifdef CHIINDEX
  1463. if ( m_bAnimation )
  1464. #endif
  1465. NextAnimation();
  1466. m_pStorageAssociativeLinks->Commit(STGC_DEFAULT);
  1467. #ifdef CHIINDEX
  1468. if ( m_bAnimation )
  1469. #endif
  1470. NextAnimation();
  1471. // Database == $OBJINST file
  1472. m_pPersistStorageDatabase->Save( m_pFileSystem->GetITStorageDocObj(), TRUE );
  1473. #ifdef CHIINDEX
  1474. if ( m_bAnimation )
  1475. #endif
  1476. NextAnimation();
  1477. m_pPersistStorageDatabase->Release();
  1478. m_pPersistStorageDatabase = NULL;
  1479. #ifdef CHIINDEX
  1480. if ( m_bAnimation )
  1481. #endif
  1482. NextAnimation();
  1483. m_pFileSystem->GetITStorageDocObj()->Commit(STGC_DEFAULT);
  1484. #ifdef CHIINDEX
  1485. if ( m_bAnimation )
  1486. #endif
  1487. NextAnimation();
  1488. }
  1489. return hr;
  1490. }