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.

950 lines
28 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. //
  5. // Copyright (C) Microsoft Corporation, 1999 - 1999
  6. //
  7. // File: helpdoc.cpp
  8. //
  9. //--------------------------------------------------------------------------
  10. /*
  11. * There are two ways by which help collection is recognized dirty. First is if a snapin
  12. * is added/removed or extension is enabled/disabled, but this is only for this instance
  13. * of console file.
  14. * Second is if the modification time of console file is different from modification time
  15. * of collection. This is because an author may have added/removed a snapin without bringing
  16. * up help and saves console file. So the modification time on console file is later than
  17. * collection. Next time he/she opens console file and brings help, the help collection is
  18. * regenerated.
  19. */
  20. // mmchelp.cpp : implmentation of the HelpTopics class
  21. //
  22. #include "stdafx.h"
  23. #include "strings.h"
  24. #include "helpdoc.h"
  25. #include "nodemgr.h"
  26. #include "regutil.h"
  27. #include "scopndcb.h"
  28. #ifdef DBG
  29. CTraceTag tagHelpCollection (_T("Help"), _T(".COL construction"));
  30. #endif
  31. BOOL GetBaseFileName(LPCWSTR pszFilePath, LPWSTR pszBaseName, int cBaseName);
  32. BOOL MatchFileTimes(FILETIME& ftime1, FILETIME& ftime2);
  33. HRESULT CHelpDoc::Initialize(HELPDOCINFO* pDocInfo)
  34. {
  35. ASSERT(pDocInfo != NULL);
  36. m_pDocInfo = pDocInfo;
  37. return BuildFilePath();
  38. }
  39. HRESULT CHelpDoc::BuildFilePath()
  40. {
  41. USES_CONVERSION;
  42. do // false loop
  43. {
  44. // Get temp directory
  45. DWORD dwRet = GetTempPath(MAX_PATH, m_szFilePath);
  46. if (dwRet == 0 || dwRet > MAX_PATH)
  47. break;
  48. // Make sure that the temp path exists and it is a dir
  49. dwRet = GetFileAttributes(m_szFilePath);
  50. if ( (0xFFFFFFFF == dwRet) || !(FILE_ATTRIBUTE_DIRECTORY & dwRet) )
  51. break;
  52. // Get base name of console file (if no name use "default")
  53. WCHAR szBaseName[MAX_PATH];
  54. if (m_pDocInfo->m_pszFileName && m_pDocInfo->m_pszFileName[0])
  55. {
  56. TCHAR szShortFileName[MAX_PATH] = {0};
  57. if ( 0 == GetShortPathName( OLE2CT( m_pDocInfo->m_pszFileName ), szShortFileName, countof(szShortFileName) - 1 ) )
  58. wcscpy(szBaseName, L"default"); // Does not need to be localized
  59. else
  60. GetBaseFileName( T2CW(szShortFileName), szBaseName, MAX_PATH);
  61. }
  62. else
  63. {
  64. wcscpy(szBaseName, L"default"); // Does not need to be localized
  65. }
  66. TCHAR* pszBaseName = OLE2T(szBaseName);
  67. if (lstrlen(m_szFilePath) + lstrlen(pszBaseName) >= MAX_PATH - 4)
  68. break;
  69. // construct help file path
  70. lstrcat(m_szFilePath, pszBaseName);
  71. lstrcat(m_szFilePath, _T(".col"));
  72. return S_OK;
  73. } while (0);
  74. // clear path on failure
  75. m_szFilePath[0] = 0;
  76. return E_FAIL;
  77. }
  78. bool entry_title_comp(EntryPair* pE1, EntryPair* pE2)
  79. {
  80. return pE1->second < pE2->second;
  81. }
  82. //------------------------------------------------------------------------------
  83. // Enumerate the snapins in the snap-in cache. Call AddSnapInToList for each one.
  84. // Open the snap-ins registry key for AddSnapInToList to use. When all the
  85. // snap-ins have been added, sort the resulting entries by snap-in name.
  86. //------------------------------------------------------------------------------
  87. HRESULT CHelpDoc::CreateSnapInList()
  88. {
  89. DECLARE_SC(sc, TEXT("CHelpDoc::CreateSnapInList"));
  90. CSnapInsCache* pSnapInsCache = theApp.GetSnapInsCache();
  91. ASSERT(pSnapInsCache != NULL);
  92. m_entryMap.clear();
  93. m_entryList.clear();
  94. // open MMC\Snapins key
  95. sc = ScFromWin32 ( m_keySnapIns.Open(HKEY_LOCAL_MACHINE, SNAPINS_KEY, KEY_READ) );
  96. if (sc)
  97. return sc.ToHr();
  98. // mark all snapins which have external references
  99. sc = pSnapInsCache->ScMarkExternallyReferencedSnapins();
  100. if (sc)
  101. return sc.ToHr();
  102. // Add each snap-in and its static extensions to the list
  103. CSnapInsCache::iterator c_it;
  104. for (c_it = pSnapInsCache->begin(); c_it != pSnapInsCache->end(); ++c_it)
  105. {
  106. const CSnapIn *pSnapin = c_it->second;
  107. if (!pSnapin)
  108. return (sc = E_UNEXPECTED).ToHr();
  109. bool bIsExternallyReferenced = false;
  110. sc = pSnapin->ScTempState_IsExternallyReferenced( bIsExternallyReferenced );
  111. if (sc)
  112. return sc.ToHr();
  113. // skip if snapin is not externally referenced
  114. if ( !bIsExternallyReferenced )
  115. continue;
  116. AddSnapInToList(pSnapin->GetSnapInCLSID());
  117. // we do not need to add extensions, since they are in cache anyway
  118. // and must be marked as externally referenced, (so will be added by the code above)
  119. // but it is worth to assert that
  120. #ifdef DBG
  121. {
  122. CExtSI* pExt = pSnapin->GetExtensionSnapIn();
  123. while (pExt != NULL)
  124. {
  125. CSnapIn *pSnapin = pExt->GetSnapIn();
  126. sc = ScCheckPointers( pSnapin, E_UNEXPECTED );
  127. if (sc)
  128. {
  129. sc.TraceAndClear();
  130. break;
  131. }
  132. bool bExtensionExternallyReferenced = false;
  133. sc = pSnapin->ScTempState_IsExternallyReferenced( bExtensionExternallyReferenced );
  134. if (sc)
  135. {
  136. sc.TraceAndClear();
  137. break;
  138. }
  139. // assert it is in the cache and is marked properly
  140. CSnapInPtr spSnapin;
  141. ASSERT( SC(S_OK) == pSnapInsCache->ScFindSnapIn( pExt->GetCLSID(), &spSnapin ) );
  142. ASSERT( bExtensionExternallyReferenced );
  143. pExt = pExt->Next();
  144. }
  145. }
  146. #endif // DBG
  147. }
  148. m_keySnapIns.Close();
  149. // our snap-in set is now up to date
  150. pSnapInsCache->SetHelpCollectionDirty(false);
  151. // copy items from map to list container so they can be sorted
  152. EntryMap::iterator it;
  153. for (it = m_entryMap.begin(); it != m_entryMap.end(); it++ )
  154. {
  155. m_entryList.push_back(&(*it));
  156. }
  157. sort(m_entryList.begin(), m_entryList.end(), entry_title_comp);
  158. return sc.ToHr();
  159. }
  160. //-----------------------------------------------------------------
  161. // Add an entry to the snap-in list for the specified snap-in CLSID.
  162. // Then recursively add any dynamic-only extensions that are registered
  163. // to extend this snap-in. This list is indexed by snap-in CLSID to
  164. // speed up checking for duplicate snap-ins.
  165. //-----------------------------------------------------------------
  166. void CHelpDoc::AddSnapInToList(const CLSID& rclsid)
  167. {
  168. DECLARE_SC(sc, TEXT("CHelpDoc::AddSnapInToList"));
  169. // check if already included
  170. if (m_entryMap.find(rclsid) != m_entryMap.end())
  171. return;
  172. // open the snap-in key
  173. OLECHAR szCLSID[40];
  174. int iRet = StringFromGUID2(rclsid, szCLSID, countof(szCLSID));
  175. ASSERT(iRet != 0);
  176. USES_CONVERSION;
  177. CRegKeyEx keyItem;
  178. LONG lStat = keyItem.Open(m_keySnapIns, OLE2T(szCLSID), KEY_READ);
  179. if (lStat != ERROR_SUCCESS)
  180. return;
  181. // get the snap-in name
  182. WTL::CString strName;
  183. sc = ScGetSnapinNameFromRegistry (keyItem, strName);
  184. #ifdef DBG
  185. if (sc)
  186. {
  187. USES_CONVERSION;
  188. sc.SetSnapinName(W2T (szCLSID)); // only guid is valid ...
  189. TraceSnapinError(_T("Failure reading \"NameString\" value from registry"), sc);
  190. sc.Clear();
  191. }
  192. #endif // DBG
  193. // Add to snap-in list
  194. if (lStat == ERROR_SUCCESS)
  195. {
  196. wstring s(T2COLE(strName));
  197. m_entryMap[rclsid] = s;
  198. }
  199. // Get list of registered extensions
  200. CExtensionsCache ExtCache;
  201. HRESULT hr = MMCGetExtensionsForSnapIn(rclsid, ExtCache);
  202. ASSERT(SUCCEEDED(hr));
  203. if (hr != S_OK)
  204. return;
  205. // Pass each dynamic extension to AddSnapInToList
  206. // Note that the EXT_TYPE_DYNAMIC flag will be set for any extension
  207. // that is dynamic-only for at least one nodetype. It may also be a
  208. // static extension for another node type, so we don't check that the
  209. // EXT_TYPE_STATIC flag is not set.
  210. CExtensionsCacheIterator ExtIter(ExtCache);
  211. for (; ExtIter.IsEnd() == FALSE; ExtIter.Advance())
  212. {
  213. if (ExtIter.GetValue() & CExtSI::EXT_TYPE_DYNAMIC)
  214. {
  215. CLSID clsid = ExtIter.GetKey();
  216. AddSnapInToList(clsid);
  217. }
  218. }
  219. }
  220. //----------------------------------------------------------------------
  221. // Add a single file to a help collection. The file is added as a title
  222. // and if bAddFolder is specified a folder is also added.
  223. //----------------------------------------------------------------------
  224. HRESULT CHelpDoc::AddFileToCollection(
  225. LPCWSTR pszTitle,
  226. LPCWSTR pszFilePath,
  227. BOOL bAddFolder )
  228. {
  229. DECLARE_SC (sc, _T("CHelpDoc::AddFileToCollection"));
  230. /*
  231. * redirect the help file to the user's UI language, if necessary
  232. */
  233. WTL::CString strFilePath = pszFilePath;
  234. LANGID langid = ENGLANGID;
  235. sc = ScRedirectHelpFile (strFilePath, langid);
  236. if (sc)
  237. return (sc.ToHr());
  238. Trace (tagHelpCollection, _T("AddFileToCollection: %s - %s (langid=0x%04x)"), pszTitle, (LPCTSTR) strFilePath, langid);
  239. USES_CONVERSION;
  240. pszFilePath = T2CW (strFilePath);
  241. DWORD dwError = 0;
  242. m_spCollection->AddTitle (pszTitle, pszFilePath, pszFilePath, L"", L"",
  243. langid, FALSE, NULL, &dwError, TRUE, L"");
  244. if (dwError != 0)
  245. return ((sc = E_FAIL).ToHr());
  246. if (bAddFolder)
  247. {
  248. // Folder ID parameter has the form "=title"
  249. WCHAR szTitleEq[MAX_PATH+1];
  250. szTitleEq[0] = L'=';
  251. wcscpy(szTitleEq+1, pszTitle);
  252. m_spCollection->AddFolder(szTitleEq, 1, &dwError, langid);
  253. if (dwError != 0)
  254. return ((sc = E_FAIL).ToHr());
  255. }
  256. return (sc.ToHr());
  257. }
  258. /*+-------------------------------------------------------------------------*
  259. * CHelpDoc::ScRedirectHelpFile
  260. *
  261. * This method is for MUI support. On MUI systems, where the user's UI
  262. * language is not US English, we will attempt to redirect the help file to
  263. *
  264. * <dir>\mui\<langid>\<helpfile>
  265. *
  266. * <dir> Takes one of two values: If the helpfile passed in is fully
  267. * qualified, <dir> is the supplied directory. If the helpfile
  268. * passed in is unqualified, then <dir> is %SystemRoot%\Help.
  269. * <langid> The langid of the user's UI language, formatted as %04x
  270. * <helpfile> The name of the original .chm file.
  271. *
  272. * This function returns:
  273. *
  274. * S_OK if the helpfile was successfully redirected
  275. * S_FALSE if the helpfile wasn't redirected
  276. *--------------------------------------------------------------------------*/
  277. SC CHelpDoc::ScRedirectHelpFile (
  278. WTL::CString& strHelpFile, /* I/O:help file (maybe redirected) */
  279. LANGID& langid) /* O:language ID of output help file */
  280. {
  281. DECLARE_SC (sc, _T("CHelpDoc::ScRedirectHelpFile"));
  282. typedef LANGID (WINAPI* GetUILangFunc)(void);
  283. static GetUILangFunc GetUserDefaultUILanguage_ = NULL;
  284. static GetUILangFunc GetSystemDefaultUILanguage_ = NULL;
  285. static bool fAttemptedGetProcAddress = false;
  286. /*
  287. * validate input
  288. */
  289. if (strHelpFile.IsEmpty())
  290. return (sc = E_FAIL);
  291. /*
  292. * assume no redirection is required
  293. */
  294. sc = S_FALSE;
  295. langid = ENGLANGID;
  296. Trace (tagHelpCollection, _T("Checking for redirection of %s"), (LPCTSTR) strHelpFile);
  297. /*
  298. * GetUser/SystemDefaultUILanguage are unsupported on systems < Win2K,
  299. * so load them dynamically
  300. */
  301. if (!fAttemptedGetProcAddress)
  302. {
  303. fAttemptedGetProcAddress = true;
  304. HMODULE hMod = GetModuleHandle (_T("kernel32.dll"));
  305. if (hMod)
  306. {
  307. GetUserDefaultUILanguage_ = (GetUILangFunc) GetProcAddress (hMod, "GetUserDefaultUILanguage");
  308. GetSystemDefaultUILanguage_ = (GetUILangFunc) GetProcAddress (hMod, "GetSystemDefaultUILanguage");
  309. }
  310. }
  311. /*
  312. * if we couldn't load the MUI APIs, don't redirect
  313. */
  314. if ((GetUserDefaultUILanguage_ == NULL) || (GetSystemDefaultUILanguage_ == NULL))
  315. {
  316. Trace (tagHelpCollection, _T("Couldn't load GetUser/SystemDefaultUILanguage, not redirecting"));
  317. return (sc);
  318. }
  319. /*
  320. * find out what languages the system and user are using
  321. */
  322. const LANGID langidUser = GetUserDefaultUILanguage_();
  323. const LANGID langidSystem = GetSystemDefaultUILanguage_();
  324. /*
  325. * we only redirect if we're running on MUI and MUI is always hosted on
  326. * the US English release, so if the system UI language isn't US English,
  327. * don't redirect
  328. */
  329. if (langidSystem != ENGLANGID)
  330. {
  331. langid = langidSystem;
  332. Trace (tagHelpCollection, _T("System UI language is not US English (0x%04x), not redirecting"), langidSystem);
  333. return (sc);
  334. }
  335. /*
  336. * if the user's language is US English, don't redirect
  337. */
  338. if (langidUser == ENGLANGID)
  339. {
  340. Trace (tagHelpCollection, _T("User's UI language is US English, not redirecting"));
  341. return (sc);
  342. }
  343. /*
  344. * the user's language is different from the default, see if we can
  345. * find a help file that matches the user's UI langugae
  346. */
  347. ASSERT (langidUser != langidSystem);
  348. WTL::CString strName;
  349. WTL::CString strPathPrefix;
  350. /*
  351. * look for a path seperator to see if this is a fully qualified filename
  352. */
  353. int iLastSep = strHelpFile.ReverseFind (_T('\\'));
  354. /*
  355. * if it's fully qualified, construct a MUI directory name, e.g.
  356. *
  357. * <path>\mui\<langid>\<filename>
  358. */
  359. if (iLastSep != -1)
  360. {
  361. strName = strHelpFile.Mid (iLastSep+1);
  362. strPathPrefix = strHelpFile.Left (iLastSep);
  363. }
  364. /*
  365. * otherwise, it's not fully qualified, default to %SystemRoot%\Help, e.g.
  366. *
  367. * %SystemRoot%\Help\mui\<langid>\<filename>
  368. */
  369. else
  370. {
  371. strName = strHelpFile;
  372. ExpandEnvironmentStrings (_T("%SystemRoot%\\Help"),
  373. strPathPrefix.GetBuffer(MAX_PATH), MAX_PATH);
  374. strPathPrefix.ReleaseBuffer();
  375. }
  376. WTL::CString strRedirectedHelpFile;
  377. strRedirectedHelpFile.Format (_T("%s\\mui\\%04x\\%s"),
  378. (LPCTSTR) strPathPrefix,
  379. langidUser,
  380. (LPCTSTR) strName);
  381. /*
  382. * see if the redirected help file exists
  383. */
  384. DWORD dwAttr = GetFileAttributes (strRedirectedHelpFile);
  385. if ((dwAttr == 0xFFFFFFFF) ||
  386. (dwAttr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_OFFLINE)))
  387. {
  388. #ifdef DBG
  389. Trace (tagHelpCollection, _T("Attempting redirection to %s, %s"),
  390. (LPCTSTR) strRedirectedHelpFile,
  391. (dwAttr == 0xFFFFFFFF) ? _T("not found") :
  392. (dwAttr & FILE_ATTRIBUTE_DIRECTORY) ? _T("found as directory") :
  393. _T("file offline"));
  394. #endif
  395. return (sc);
  396. }
  397. /*
  398. * if we get here, we've found a help file that matches the user's UI
  399. * language; return it and the UI language ID
  400. */
  401. Trace (tagHelpCollection, _T("Help redirected to %s"), (LPCTSTR) strRedirectedHelpFile);
  402. strHelpFile = strRedirectedHelpFile;
  403. langid = langidUser;
  404. /*
  405. * we redirected, return S_OK
  406. */
  407. return (sc = S_OK);
  408. }
  409. //-------------------------------------------------------------------------------
  410. // Delete the current help file collection. First delete it as a collection, then
  411. // delete the file itself. It is possible that the file doesn't exist when this
  412. // is called, so it's not a failure if it can't be deleted.
  413. //-------------------------------------------------------------------------------
  414. void
  415. CHelpDoc::DeleteHelpFile()
  416. {
  417. // Delete existing help file
  418. HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
  419. FILE_ATTRIBUTE_NORMAL, NULL);
  420. if (hFile != INVALID_HANDLE_VALUE)
  421. {
  422. ::CloseHandle(hFile);
  423. IHHCollectionWrapperPtr spOldCollection;
  424. spOldCollection.CreateInstance(CLSID_HHCollectionWrapper);
  425. USES_CONVERSION;
  426. WCHAR* pszFilePath = T2OLE(m_szFilePath);
  427. DWORD dwError = spOldCollection->Open(pszFilePath);
  428. if (dwError == 0)
  429. spOldCollection->RemoveCollection(FALSE);
  430. ::DeleteFile(m_szFilePath);
  431. }
  432. }
  433. //----------------------------------------------------------------------------
  434. // Create a new help doc file for the current MMC console. This function
  435. // enumerates all of the snap-in's used in the console and all their possible
  436. // extension snap-ins. It queries each snap-in for a single help topic file and
  437. // any linked help files. These files are added to a collection file which
  438. // is then saved with the same base file name, creation time, and modification
  439. // time as the console file.
  440. //-----------------------------------------------------------------------------
  441. HRESULT CHelpDoc::CreateHelpFile()
  442. {
  443. USES_CONVERSION;
  444. HelpCollectionEntrySet HelpFiles;
  445. DWORD dwError;
  446. ASSERT(m_spCollection == NULL);
  447. m_spCollection.CreateInstance(CLSID_HHCollectionWrapper);
  448. ASSERT(m_spCollection != NULL);
  449. if (m_spCollection == NULL)
  450. return E_FAIL;
  451. HRESULT hr = CreateSnapInList();
  452. if (hr != S_OK)
  453. return hr;
  454. IMallocPtr spIMalloc;
  455. hr = CoGetMalloc(MEMCTX_TASK, &spIMalloc);
  456. ASSERT(hr == S_OK);
  457. if (hr != S_OK)
  458. return hr;
  459. // Delete existing file before rebuilding it, or help files will
  460. // be appended to the existing files
  461. DeleteHelpFile();
  462. // open new collection file
  463. WCHAR* pszFilePath = T2OLE(m_szFilePath);
  464. dwError = m_spCollection->Open(pszFilePath);
  465. ASSERT(dwError == 0);
  466. if (dwError != 0)
  467. return E_FAIL;
  468. // Have collection automatically find linked files
  469. m_spCollection->SetFindMergedCHMS(TRUE);
  470. AddFileToCollection(L"mmc", T2CW(SC::GetHelpFile()), TRUE);
  471. /*
  472. * Build a set of unique help files provided by the snap-ins
  473. */
  474. EntryPtrList::iterator it;
  475. for (it = m_entryList.begin(); it != m_entryList.end(); ++it)
  476. {
  477. TRACE(_T("Help snap-in: %s\n"), (*it)->second.c_str());
  478. USES_CONVERSION;
  479. HRESULT hr;
  480. OLECHAR szHelpFilePath[MAX_PATH];
  481. const CLSID& clsid = (*it)->first;
  482. // Create an instance of the snap-in to query
  483. IUnknownPtr spIUnknown;
  484. hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC, IID_IUnknown, (void**)&spIUnknown);
  485. if (FAILED(hr))
  486. continue;
  487. // use either ISnapinHelp or ISnapinHelp2 to get the main topic file
  488. ISnapinHelpPtr spIHelp = spIUnknown;
  489. ISnapinHelp2Ptr spIHelp2 = spIUnknown;
  490. if (spIHelp == NULL && spIHelp2 == NULL)
  491. continue;
  492. LPWSTR pszHelpFile = NULL;
  493. hr = (spIHelp2 != NULL) ? spIHelp2->GetHelpTopic(&pszHelpFile) :
  494. spIHelp->GetHelpTopic(&pszHelpFile);
  495. if (hr == S_OK)
  496. {
  497. /*
  498. * Put this help file in the collection entry set. The
  499. * set will prevent duplicating help file names.
  500. */
  501. HelpFiles.insert (CHelpCollectionEntry (pszHelpFile, clsid));
  502. spIMalloc->Free(pszHelpFile);
  503. // if IsnapinHelp2, query for additional help files
  504. pszHelpFile = NULL;
  505. if (spIHelp2 == NULL ||
  506. spIHelp2->GetLinkedTopics(&pszHelpFile) != S_OK ||
  507. pszHelpFile == NULL)
  508. continue;
  509. // There may be multiple names separated by ';'s
  510. // Add each as a separate title.
  511. // Note: there is no call to AddFolder because linked files
  512. // do not appear in the TOC.
  513. WCHAR *pchStart = wcstok(pszHelpFile, L";");
  514. while (pchStart != NULL)
  515. {
  516. // Must use base file name as title ID
  517. WCHAR szTitleID[MAX_PATH];
  518. if (GetBaseFileName(pchStart, szTitleID, MAX_PATH))
  519. {
  520. AddFileToCollection(szTitleID, pchStart, FALSE);
  521. }
  522. // position to start of next string
  523. pchStart = wcstok(NULL, L";");
  524. }
  525. spIMalloc->Free(pszHelpFile);
  526. }
  527. }
  528. /*
  529. * Put all of the help files provided by the snap-ins in the help collection.
  530. */
  531. HelpCollectionEntrySet::iterator itHelpFile;
  532. for (itHelpFile = HelpFiles.begin(); itHelpFile != HelpFiles.end(); ++itHelpFile)
  533. {
  534. const CHelpCollectionEntry& file = *itHelpFile;
  535. AddFileToCollection(file.m_strCLSID.c_str(), file.m_strHelpFile.c_str(), TRUE);
  536. }
  537. dwError = m_spCollection->Save();
  538. ASSERT(dwError == 0);
  539. dwError = m_spCollection->Close();
  540. ASSERT(dwError == 0);
  541. // Force creation/modify times to match the console file
  542. HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
  543. FILE_ATTRIBUTE_NORMAL, NULL);
  544. ASSERT(hFile != INVALID_HANDLE_VALUE);
  545. if (hFile != INVALID_HANDLE_VALUE)
  546. {
  547. BOOL bStat = ::SetFileTime(hFile, &m_pDocInfo->m_ftimeCreate, NULL, &m_pDocInfo->m_ftimeModify);
  548. ASSERT(bStat);
  549. ::CloseHandle(hFile);
  550. ASSERT(IsHelpFileValid());
  551. }
  552. return S_OK;
  553. }
  554. //-----------------------------------------------------------------------------
  555. // Determine if the current help doc file is valid. A help file is valid if it
  556. // has the base file name, creation time, and modification time as the MMC
  557. // console doc file.
  558. //-----------------------------------------------------------------------------
  559. BOOL CHelpDoc::IsHelpFileValid()
  560. {
  561. // Try to open the help file
  562. HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING,
  563. FILE_ATTRIBUTE_NORMAL, NULL);
  564. if (hFile == INVALID_HANDLE_VALUE)
  565. return FALSE;
  566. // Check file creation and modification times
  567. FILETIME ftimeCreate;
  568. FILETIME ftimeModify;
  569. BOOL bStat = ::GetFileTime(hFile, &ftimeCreate, NULL, &ftimeModify);
  570. ASSERT(bStat);
  571. ::CloseHandle(hFile);
  572. return MatchFileTimes(ftimeCreate,m_pDocInfo->m_ftimeCreate) &&
  573. MatchFileTimes(ftimeModify,m_pDocInfo->m_ftimeModify);
  574. }
  575. //--------------------------------------------------------------------------
  576. // If the current help doc file is valid then update its creation and
  577. // modification times to match the new doc info.
  578. //--------------------------------------------------------------------------
  579. HRESULT CHelpDoc::UpdateHelpFile(HELPDOCINFO* pNewDocInfo)
  580. {
  581. if (IsHelpFileValid())
  582. {
  583. HANDLE hFile = ::CreateFile(m_szFilePath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
  584. FILE_ATTRIBUTE_NORMAL, NULL);
  585. if (hFile == INVALID_HANDLE_VALUE)
  586. return E_FAIL;
  587. BOOL bStat = ::SetFileTime(hFile, &pNewDocInfo->m_ftimeCreate, NULL, &pNewDocInfo->m_ftimeModify);
  588. ASSERT(bStat);
  589. ::CloseHandle(hFile);
  590. }
  591. return S_OK;
  592. }
  593. //------------------------------------------------------------------------
  594. // Delete the current help doc file
  595. //------------------------------------------------------------------------
  596. HRESULT CNodeCallback::OnDeleteHelpDoc(HELPDOCINFO* pCurDocInfo)
  597. {
  598. CHelpDoc HelpDoc;
  599. HRESULT hr = HelpDoc.Initialize(pCurDocInfo);
  600. if (FAILED(hr))
  601. return hr;
  602. HelpDoc.DeleteHelpFile();
  603. return S_OK;
  604. }
  605. CHelpCollectionEntry::CHelpCollectionEntry(LPOLESTR pwzHelpFile, const CLSID& clsid)
  606. {
  607. if (!IsPartOfString (m_strHelpFile, pwzHelpFile))
  608. m_strHelpFile.erase(); // see KB Q172398
  609. m_strHelpFile = pwzHelpFile;
  610. WCHAR szCLSID[40];
  611. StringFromGUID2 (clsid, szCLSID, countof(szCLSID));
  612. m_strCLSID.erase(); // see KB Q172398
  613. m_strCLSID = szCLSID;
  614. }
  615. // ----------------------------------------------------------------------
  616. // CNodeCallack method implementation
  617. // ----------------------------------------------------------------------
  618. //------------------------------------------------------------------------
  619. // Get the pathname of the help doc for an MMC console doc. If the current
  620. // help doc is valid and there are no snap-in changes, return the current
  621. // doc. Otherwise, create a new help doc and return it.
  622. //------------------------------------------------------------------------
  623. HRESULT CNodeCallback::OnGetHelpDoc(HELPDOCINFO* pHelpInfo, LPOLESTR* ppszHelpFile)
  624. {
  625. CHelpDoc HelpDoc;
  626. HRESULT hr = HelpDoc.Initialize(pHelpInfo);
  627. if (FAILED(hr))
  628. return hr;
  629. CSnapInsCache* pSnapInsCache = theApp.GetSnapInsCache();
  630. ASSERT(pSnapInsCache != NULL);
  631. hr = S_OK;
  632. // Rebuild file if snap-in set changed or current file is not up to date
  633. if (pSnapInsCache->IsHelpCollectionDirty() || !HelpDoc.IsHelpFileValid())
  634. {
  635. hr = HelpDoc.CreateHelpFile();
  636. }
  637. // if ok, allocate and return file path string (OLESTR)
  638. if (SUCCEEDED(hr))
  639. {
  640. *ppszHelpFile = reinterpret_cast<LPOLESTR>
  641. (CoTaskMemAlloc((lstrlen(HelpDoc.GetFilePath()) + 1) * sizeof(wchar_t)));
  642. if (*ppszHelpFile == NULL)
  643. return E_OUTOFMEMORY;
  644. USES_CONVERSION;
  645. wcscpy(*ppszHelpFile, T2COLE(HelpDoc.GetFilePath()));
  646. }
  647. return hr;
  648. }
  649. //+-------------------------------------------------------------------
  650. //
  651. // Member: CNodeCallback::DoesStandardSnapinHelpExist
  652. //
  653. // Synopsis: Given the selection context, see if Standard MMC style help
  654. // exists (snapin implements ISnapinHelp[2] interface.
  655. // If not we wantto put "Help On <Snapin> which is MMC1.0 legacy
  656. // help mechanism.
  657. //
  658. // Arguments: [hNode] - [in] the node selection context.
  659. // [bStandardHelpExists] - [out] Does standard help exists or not?
  660. //
  661. // Returns: HRESULT
  662. //
  663. //--------------------------------------------------------------------
  664. HRESULT
  665. CNodeCallback::DoesStandardSnapinHelpExist(HNODE hNode, bool& bStandardHelpExists)
  666. {
  667. DECLARE_SC(sc, TEXT("CNodeCallback::OnHasHelpDoc"));
  668. sc = ScCheckPointers( (void*) hNode);
  669. if (sc)
  670. return sc.ToHr();
  671. CNode *pNode = CNode::FromHandle(hNode);
  672. sc = ScCheckPointers(pNode, E_UNEXPECTED);
  673. if (sc)
  674. return sc.ToHr();
  675. bStandardHelpExists = false;
  676. // QI ComponentData for ISnapinHelp
  677. CMTNode* pMTNode = pNode->GetMTNode();
  678. sc = ScCheckPointers(pMTNode, E_UNEXPECTED);
  679. if(sc)
  680. return sc.ToHr();
  681. CComponentData* pCD = pMTNode->GetPrimaryComponentData();
  682. sc = ScCheckPointers(pCD, E_UNEXPECTED);
  683. if(sc)
  684. return sc.ToHr();
  685. IComponentData *pIComponentData = pCD->GetIComponentData();
  686. sc = ScCheckPointers(pIComponentData, E_UNEXPECTED);
  687. if (sc)
  688. return sc.ToHr();
  689. ISnapinHelp* pIHelp = NULL;
  690. sc = pIComponentData->QueryInterface(IID_ISnapinHelp, (void**)&pIHelp);
  691. // if no ISnapinHelp, try ISnapinHelp2
  692. if (sc)
  693. {
  694. sc = pIComponentData->QueryInterface(IID_ISnapinHelp2, (void**)&pIHelp);
  695. if (sc)
  696. {
  697. // no ISnapinHelp2 either
  698. sc.Clear(); // not an error.
  699. return sc.ToHr();
  700. }
  701. }
  702. // make sure we got a valid pointer
  703. sc = ScCheckPointers(pIHelp, E_UNEXPECTED);
  704. if(sc)
  705. {
  706. sc.Clear();
  707. return sc.ToHr();
  708. }
  709. bStandardHelpExists = true;
  710. pIHelp->Release();
  711. return (sc).ToHr();
  712. }
  713. //-----------------------------------------------------------------------
  714. // Update the current help doc file to match the new MMC console doc
  715. //-----------------------------------------------------------------------
  716. HRESULT CNodeCallback::OnUpdateHelpDoc(HELPDOCINFO* pCurDocInfo, HELPDOCINFO* pNewDocInfo)
  717. {
  718. CHelpDoc HelpDoc;
  719. HRESULT hr = HelpDoc.Initialize(pCurDocInfo);
  720. if (FAILED(hr))
  721. return hr;
  722. return HelpDoc.UpdateHelpFile(pNewDocInfo);
  723. }
  724. BOOL GetBaseFileName(LPCWSTR pszFilePath, LPWSTR pszBaseName, int cBaseName)
  725. {
  726. ASSERT(pszFilePath != NULL && pszBaseName != NULL);
  727. // Find last '\'
  728. LPCWSTR pszTemp = wcsrchr(pszFilePath, L'\\');
  729. // if no '\' found, find drive letter terminator':'
  730. if (pszTemp == NULL)
  731. pszTemp = wcsrchr(pszFilePath, L':');
  732. // if neither found, there is no path
  733. // else skip over last char of path
  734. if (pszTemp == NULL)
  735. pszTemp = pszFilePath;
  736. else
  737. pszTemp++;
  738. // find last '.' (assume that extension follows)
  739. WCHAR *pchExtn = wcsrchr(pszTemp, L'.');
  740. // How many chars excluding extension ?
  741. int cCnt = pchExtn ? (pchExtn - pszTemp) : wcslen(pszTemp);
  742. ASSERT(cBaseName > cCnt);
  743. if (cBaseName <= cCnt)
  744. return FALSE;
  745. // Copy to output buffer
  746. memcpy(pszBaseName, pszTemp, cCnt * sizeof(WCHAR));
  747. pszBaseName[cCnt] = L'\0';
  748. return TRUE;
  749. }
  750. //
  751. // Compare two file times. Two file times are a match if they
  752. // differ by no more than 2 seconds. This difference is allowed
  753. // because a FAT file system stores times with a 2 sec resolution.
  754. //
  755. inline BOOL MatchFileTimes(FILETIME& ftime1, FILETIME& ftime2)
  756. {
  757. // file system time resolution (2 sec) in 100's of nanosecs
  758. const static LONGLONG FileTimeResolution = 20000000;
  759. LONGLONG& ll1 = *(LONGLONG*)&ftime1;
  760. LONGLONG& ll2 = *(LONGLONG*)&ftime2;
  761. return (abs(ll1 - ll2) <= FileTimeResolution);
  762. }