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.

1024 lines
32 KiB

  1. // Copyright 1998 Microsoft
  2. #include "priv.h"
  3. #include "autocomp.h"
  4. #define AC_GIVEUP_COUNT 1000
  5. #define AC_TIMEOUT (60 * 1000)
  6. //
  7. // Thread messages
  8. //
  9. enum
  10. {
  11. ACM_FIRST = WM_USER,
  12. ACM_STARTSEARCH,
  13. ACM_STOPSEARCH,
  14. ACM_SETFOCUS,
  15. ACM_KILLFOCUS,
  16. ACM_QUIT,
  17. ACM_LAST,
  18. };
  19. // Special prefixes that we optionally filter out
  20. const struct{
  21. int cch;
  22. LPCWSTR psz;
  23. }
  24. g_rgSpecialPrefix[] =
  25. {
  26. {4, L"www."},
  27. {11, L"http://www."}, // This must be before "http://"
  28. {7, L"http://"},
  29. {8, L"https://"},
  30. };
  31. //+-------------------------------------------------------------------------
  32. // CACString functions - Hold autocomplete strings
  33. //--------------------------------------------------------------------------
  34. ULONG CACString::AddRef()
  35. {
  36. return InterlockedIncrement(&m_cRef);
  37. }
  38. ULONG CACString::Release()
  39. {
  40. ASSERT(m_cRef > 0);
  41. if (InterlockedDecrement(&m_cRef))
  42. {
  43. return m_cRef;
  44. }
  45. delete this;
  46. return 0;
  47. }
  48. CACString* CreateACString(LPCWSTR pszStr, int iIgnore, ULONG ulSortIndex)
  49. {
  50. ASSERT(pszStr);
  51. int cChars = lstrlen(pszStr);
  52. // Allocate the CACString class with enough room for the new string
  53. CACString* pStr = (CACString*)LocalAlloc(LPTR, cChars * sizeof(WCHAR) + sizeof(CACString));
  54. if (pStr)
  55. {
  56. StrCpy(pStr->m_sz, pszStr);
  57. pStr->m_ulSortIndex = ulSortIndex;
  58. pStr->m_cRef = 1;
  59. pStr->m_cChars = cChars;
  60. pStr->m_iIgnore = iIgnore;
  61. }
  62. return pStr;
  63. }
  64. int CACString::CompareSortingIndex(CACString& r)
  65. {
  66. int iRet;
  67. // If the sorting indices are equal, just do a string compare
  68. if (m_ulSortIndex == r.m_ulSortIndex)
  69. {
  70. iRet = StrCmpI(r);
  71. }
  72. else
  73. {
  74. iRet = (m_ulSortIndex > r.m_ulSortIndex) ? 1 : -1;
  75. }
  76. return iRet;
  77. }
  78. HRESULT CACThread::QueryInterface(REFIID riid, void **ppvObj)
  79. {
  80. static const QITAB qit[] = { { 0 }, };
  81. return QISearch(this, qit, riid, ppvObj);
  82. }
  83. ULONG CACThread::AddRef(void)
  84. {
  85. return InterlockedIncrement(&m_cRef);
  86. }
  87. ULONG CACThread::Release(void)
  88. {
  89. ASSERT(m_cRef > 0);
  90. if (InterlockedDecrement(&m_cRef))
  91. {
  92. return m_cRef;
  93. }
  94. delete this;
  95. return 0;
  96. }
  97. CACThread::CACThread(CAutoComplete& rAutoComp) : m_pAutoComp(&rAutoComp), m_cRef(1)
  98. {
  99. ASSERT(!m_fWorkItemQueued);
  100. ASSERT(!m_idThread);
  101. ASSERT(!m_hCreateEvent);
  102. ASSERT(!m_fDisabled);
  103. ASSERT(!m_pszSearch);
  104. ASSERT(!m_hdpa_list);
  105. ASSERT(!m_pes);
  106. ASSERT(!m_pacl);
  107. DllAddRef();
  108. }
  109. CACThread::~CACThread()
  110. {
  111. SyncShutDownBGThread(); // In case somehow
  112. // These should have been freed.
  113. ASSERT(!m_idThread);
  114. ASSERT(!m_hdpa_list);
  115. SAFERELEASE(m_pes);
  116. SAFERELEASE(m_peac);
  117. SAFERELEASE(m_pacl);
  118. DllRelease();
  119. }
  120. BOOL CACThread::Init(IEnumString* pes, // source of the autocomplete strings
  121. IACList* pacl) // optional interface to call Expand
  122. {
  123. // REARCHITECT: We need to marshal these interfaces to this thread!
  124. ASSERT(pes);
  125. m_pes = pes;
  126. m_pes->AddRef();
  127. m_peac = NULL;
  128. pes->QueryInterface(IID_PPV_ARG(IEnumACString, &m_peac));
  129. if (pacl)
  130. {
  131. m_pacl = pacl;
  132. m_pacl->AddRef();
  133. }
  134. return TRUE;
  135. }
  136. //+-------------------------------------------------------------------------
  137. // Called when the edit box recieves focus. We use this event to create
  138. // a background thread or to keep the backgroung thread from shutting down
  139. //--------------------------------------------------------------------------
  140. void CACThread::GotFocus()
  141. {
  142. TraceMsg(AC_GENERAL, "CACThread::GotFocus()");
  143. // Should not be NULL if the foreground thread is calling us!
  144. ASSERT(m_pAutoComp);
  145. //
  146. // Check to see if autocomplete is supposed to be enabled.
  147. //
  148. if (m_pAutoComp && m_pAutoComp->IsEnabled())
  149. {
  150. m_fDisabled = FALSE;
  151. if (m_fWorkItemQueued)
  152. {
  153. // If the thread hasn't started yet, wait for a thread creation event
  154. if (0 == m_idThread && m_hCreateEvent)
  155. {
  156. WaitForSingleObject(m_hCreateEvent, 1000);
  157. }
  158. if (m_idThread)
  159. {
  160. //
  161. // Tell the thread to cancel its timeout and stay alive.
  162. //
  163. // REARCHITECT: We have a race condition here. The thread can be
  164. // in the process of shutting down!
  165. PostThreadMessage(m_idThread, ACM_SETFOCUS, 0, 0);
  166. }
  167. }
  168. else
  169. {
  170. //
  171. // The background thread signals an event when it starts up.
  172. // We wait on this event before trying a synchronous shutdown
  173. // because any posted messages would be lost.
  174. //
  175. if (NULL == m_hCreateEvent)
  176. {
  177. m_hCreateEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  178. }
  179. else
  180. {
  181. ResetEvent(m_hCreateEvent);
  182. }
  183. //
  184. // Make sure we have a background search thread.
  185. //
  186. // If we start it any later, we run the risk of not
  187. // having its message queue available by the time
  188. // we post a message to it.
  189. //
  190. // AddRef ourselves now, to prevent us getting freed
  191. // before the thread proc starts running.
  192. //
  193. AddRef();
  194. // Call to Shlwapi thread pool
  195. if (SHQueueUserWorkItem(_ThreadProc,
  196. this,
  197. 0,
  198. (DWORD_PTR)NULL,
  199. NULL,
  200. "browseui.dll",
  201. TPS_LONGEXECTIME | TPS_DEMANDTHREAD
  202. ))
  203. {
  204. InterlockedExchange(&m_fWorkItemQueued, TRUE);
  205. }
  206. else
  207. {
  208. // Couldn't get thread
  209. Release();
  210. }
  211. }
  212. }
  213. else
  214. {
  215. m_fDisabled = TRUE;
  216. _SendAsyncShutDownMsg(FALSE);
  217. }
  218. }
  219. //+-------------------------------------------------------------------------
  220. // Called when the edit box loses focus.
  221. //--------------------------------------------------------------------------
  222. void CACThread::LostFocus()
  223. {
  224. TraceMsg(AC_GENERAL, "CACThread::LostFocus()");
  225. //
  226. // If there is a thread around, tell it to stop searching.
  227. //
  228. if (m_idThread)
  229. {
  230. StopSearch();
  231. PostThreadMessage(m_idThread, ACM_KILLFOCUS, 0, 0);
  232. }
  233. }
  234. //+-------------------------------------------------------------------------
  235. // Sends the search request to the background thread.
  236. //--------------------------------------------------------------------------
  237. BOOL CACThread::StartSearch
  238. (
  239. LPCWSTR pszSearch, // String to search
  240. DWORD dwOptions // ACO_* flags
  241. )
  242. {
  243. BOOL fRet = FALSE;
  244. // If the thread hasn't started yet, wait for a thread creation event
  245. if (0 == m_idThread && m_fWorkItemQueued && m_hCreateEvent)
  246. {
  247. WaitForSingleObject(m_hCreateEvent, 1000);
  248. }
  249. if (m_idThread)
  250. {
  251. LPWSTR pszSrch = StrDup(pszSearch);
  252. if (pszSrch)
  253. {
  254. //
  255. // This is being sent to another thread, remove it from this thread's
  256. // memlist.
  257. //
  258. //
  259. // If the background thread is already searching, abort that search
  260. //
  261. StopSearch();
  262. //
  263. // Send request off to the background search thread.
  264. //
  265. if (PostThreadMessage(m_idThread, ACM_STARTSEARCH, dwOptions, (LPARAM)pszSrch))
  266. {
  267. fRet = TRUE;
  268. }
  269. else
  270. {
  271. TraceMsg(AC_GENERAL, "CACThread::_StartSearch could not send message to thread!");
  272. LocalFree(pszSrch);
  273. }
  274. }
  275. }
  276. return fRet;
  277. }
  278. //+-------------------------------------------------------------------------
  279. // Tells the background thread to stop and pending search
  280. //--------------------------------------------------------------------------
  281. void CACThread::StopSearch()
  282. {
  283. TraceMsg(AC_GENERAL, "CACThread::_StopSearch()");
  284. //
  285. // Tell the thread to stop.
  286. //
  287. if (m_idThread)
  288. {
  289. PostThreadMessage(m_idThread, ACM_STOPSEARCH, 0, 0);
  290. }
  291. }
  292. //+-------------------------------------------------------------------------
  293. // Posts a quit message to the background thread
  294. //--------------------------------------------------------------------------
  295. void CACThread::_SendAsyncShutDownMsg(BOOL fFinalShutDown)
  296. {
  297. if (0 == m_idThread && m_fWorkItemQueued && m_hCreateEvent)
  298. {
  299. //
  300. // Make sure that the thread has started up before posting a quit
  301. // message or the quit message will be lost!
  302. //
  303. WaitForSingleObject(m_hCreateEvent, 3000);
  304. }
  305. if (m_idThread)
  306. {
  307. // Stop the search because it can hold up the thread for quite a
  308. // while by waiting for disk data.
  309. StopSearch();
  310. // Tell the thread to go away, we won't be needing it anymore. Note that we pass
  311. // the dropdown window because during the final shutdown we need to asynchronously
  312. // destroy the dropdown to avoid a crash. The background thread will keep browseui
  313. // mapped in memory until the dropdown is destroyed.
  314. HWND hwndDropDown = (fFinalShutDown ? m_pAutoComp->m_hwndDropDown : NULL);
  315. PostThreadMessage(m_idThread, ACM_QUIT, 0, (LPARAM)hwndDropDown);
  316. }
  317. }
  318. //+-------------------------------------------------------------------------
  319. // Synchroniously shutdown the background thread
  320. //
  321. // Note: this is no longer synchronous because we now orphan this object
  322. // when the associated autocomplet shuts down.
  323. //
  324. //--------------------------------------------------------------------------
  325. void CACThread::SyncShutDownBGThread()
  326. {
  327. _SendAsyncShutDownMsg(TRUE);
  328. // Block shutdown if background thread is about to use this variable
  329. ENTERCRITICAL;
  330. m_pAutoComp = NULL;
  331. LEAVECRITICAL;
  332. if (m_hCreateEvent)
  333. {
  334. CloseHandle(m_hCreateEvent);
  335. m_hCreateEvent = NULL;
  336. }
  337. }
  338. void CACThread::_FreeThreadData()
  339. {
  340. if (m_hdpa_list)
  341. {
  342. CAutoComplete::_FreeDPAPtrs(m_hdpa_list);
  343. m_hdpa_list = NULL;
  344. }
  345. if (m_pszSearch)
  346. {
  347. LocalFree(m_pszSearch);
  348. m_pszSearch = NULL;
  349. }
  350. InterlockedExchange(&m_idThread, 0);
  351. InterlockedExchange(&m_fWorkItemQueued, 0);
  352. }
  353. DWORD WINAPI CACThread::_ThreadProc(void *pv)
  354. {
  355. CACThread *pThis = (CACThread *)pv;
  356. HRESULT hrInit = SHCoInitialize();
  357. if (SUCCEEDED(hrInit))
  358. {
  359. pThis->_ThreadLoop();
  360. }
  361. pThis->Release();
  362. SHCoUninitialize(hrInit);
  363. return 0;
  364. }
  365. HRESULT CACThread::_ProcessMessage(MSG * pMsg, DWORD * pdwTimeout, BOOL * pfStayAlive)
  366. {
  367. TraceMsg(AC_GENERAL, "AutoCompleteThread: Message %x received.", pMsg->message);
  368. switch (pMsg->message)
  369. {
  370. case ACM_STARTSEARCH:
  371. TraceMsg(AC_GENERAL, "AutoCompleteThread: Search started.");
  372. *pdwTimeout = INFINITE;
  373. _Search((LPWSTR)pMsg->lParam, (DWORD)pMsg->wParam);
  374. TraceMsg(AC_GENERAL, "AutoCompleteThread: Search completed.");
  375. break;
  376. case ACM_STOPSEARCH:
  377. while (PeekMessage(pMsg, pMsg->hwnd, ACM_STOPSEARCH, ACM_STOPSEARCH, PM_REMOVE))
  378. {
  379. NULL;
  380. }
  381. TraceMsg(AC_GENERAL, "AutoCompleteThread: Search stopped.");
  382. break;
  383. case ACM_SETFOCUS:
  384. TraceMsg(AC_GENERAL, "AutoCompleteThread: Got Focus.");
  385. *pdwTimeout = INFINITE;
  386. break;
  387. case ACM_KILLFOCUS:
  388. TraceMsg(AC_GENERAL, "AutoCompleteThread: Lost Focus.");
  389. *pdwTimeout = AC_TIMEOUT;
  390. break;
  391. case ACM_QUIT:
  392. {
  393. TraceMsg(AC_GENERAL, "AutoCompleteThread: ACM_QUIT received.");
  394. *pfStayAlive = FALSE;
  395. //
  396. // If a hwnd was passed in then we are shutting down and we need to
  397. // wait until the dropdown window is destroyed before exiting this
  398. // thread. That way browseui will stay mapped in memory.
  399. //
  400. HWND hwndDropDown = (HWND)pMsg->lParam;
  401. if (hwndDropDown)
  402. {
  403. // We wait 5 seconds for the window to go away, checking every 100ms
  404. int cSleep = 50;
  405. while (IsWindow(hwndDropDown) && (--cSleep > 0))
  406. {
  407. MsgWaitForMultipleObjects(0, NULL, FALSE, 100, QS_TIMER);
  408. }
  409. }
  410. }
  411. break;
  412. default:
  413. // pump any ole-based window message that might also be on this thread
  414. TranslateMessage(pMsg);
  415. DispatchMessage(pMsg);
  416. break;
  417. }
  418. return S_OK;
  419. }
  420. //+-------------------------------------------------------------------------
  421. // Message pump for the background thread
  422. //--------------------------------------------------------------------------
  423. HRESULT CACThread::_ThreadLoop()
  424. {
  425. MSG Msg;
  426. DWORD dwTimeout = INFINITE;
  427. BOOL fStayAlive = TRUE;
  428. TraceMsg(AC_WARNING, "AutoComplete service thread started.");
  429. //
  430. // We need to call a window's api for a message queue to be created
  431. // so we call peekmessage. Then we get the thread id and thread handle
  432. // and we signal an event to tell the forground thread that we are listening.
  433. //
  434. while (PeekMessage(&Msg, NULL, ACM_FIRST, ACM_LAST, PM_REMOVE))
  435. {
  436. // purge any messages we care about from previous owners of this thread.
  437. }
  438. // The forground thread needs this is so that it can post us messages
  439. InterlockedExchange(&m_idThread, GetCurrentThreadId());
  440. if (m_hCreateEvent)
  441. {
  442. SetEvent(m_hCreateEvent);
  443. }
  444. HANDLE hThread = GetCurrentThread();
  445. int nOldPriority = GetThreadPriority(hThread);
  446. SetThreadPriority(hThread, THREAD_PRIORITY_BELOW_NORMAL);
  447. while (fStayAlive)
  448. {
  449. while (fStayAlive && PeekMessage(&Msg, NULL, 0, (UINT)-1, PM_NOREMOVE))
  450. {
  451. if (-1 != GetMessage(&Msg, NULL, 0, 0))
  452. {
  453. if (!Msg.hwnd)
  454. {
  455. // No hwnd means it's a thread message, so it's ours.
  456. _ProcessMessage(&Msg, &dwTimeout, &fStayAlive);
  457. }
  458. else
  459. {
  460. // It has an hwnd then it's not ours. We will not allow windows on our thread.
  461. // If anyone creates their windows on their thread, file a bug against them
  462. // to remove it.
  463. }
  464. }
  465. }
  466. if (fStayAlive)
  467. {
  468. TraceMsg(AC_GENERAL, "AutoCompleteThread: Sleeping for%s.", dwTimeout == INFINITE ? "ever" : " one minute");
  469. DWORD dwWait = MsgWaitForMultipleObjects(0, NULL, FALSE, dwTimeout, QS_ALLINPUT);
  470. #ifdef DEBUG
  471. switch (dwWait)
  472. {
  473. case 0xFFFFFFFF:
  474. ASSERT(dwWait != 0xFFFFFFFF);
  475. break;
  476. case WAIT_TIMEOUT:
  477. TraceMsg(AC_GENERAL, "AutoCompleteThread: Timeout expired.");
  478. break;
  479. }
  480. #endif
  481. fStayAlive = (dwWait == WAIT_OBJECT_0);
  482. }
  483. }
  484. TraceMsg(AC_GENERAL, "AutoCompleteThread: Thread dying.");
  485. _FreeThreadData();
  486. SetThreadPriority(hThread, nOldPriority);
  487. // Purge any remaining messages before returning this thread to the pool.
  488. while (PeekMessage(&Msg, NULL, ACM_FIRST, ACM_LAST, PM_REMOVE))
  489. {}
  490. TraceMsg(AC_WARNING, "AutoCompleteThread: Thread dead.");
  491. return S_OK;
  492. }
  493. //+-------------------------------------------------------------------------
  494. // Returns true if the search string matches one or more characters of a
  495. // prefix that we filter out matches to
  496. //--------------------------------------------------------------------------
  497. BOOL CACThread::MatchesSpecialPrefix(LPCWSTR pszSearch)
  498. {
  499. BOOL fRet = FALSE;
  500. int cchSearch = lstrlen(pszSearch);
  501. for (int i = 0; i < ARRAYSIZE(g_rgSpecialPrefix); ++i)
  502. {
  503. // See if the search string matches one or more characters of the prefix
  504. if (cchSearch <= g_rgSpecialPrefix[i].cch &&
  505. StrCmpNI(g_rgSpecialPrefix[i].psz, pszSearch, cchSearch) == 0)
  506. {
  507. fRet = TRUE;
  508. break;
  509. }
  510. }
  511. return fRet;
  512. }
  513. //+-------------------------------------------------------------------------
  514. // Returns the length of the prefix it the string starts with a special
  515. // prefix that we filter out matches to. Otherwise returns zero.
  516. //--------------------------------------------------------------------------
  517. int CACThread::GetSpecialPrefixLen(LPCWSTR psz)
  518. {
  519. int nRet = 0;
  520. int cch = lstrlen(psz);
  521. for (int i = 0; i < ARRAYSIZE(g_rgSpecialPrefix); ++i)
  522. {
  523. if (cch >= g_rgSpecialPrefix[i].cch &&
  524. StrCmpNI(g_rgSpecialPrefix[i].psz, psz, g_rgSpecialPrefix[i].cch) == 0)
  525. {
  526. nRet = g_rgSpecialPrefix[i].cch;
  527. break;
  528. }
  529. }
  530. return nRet;
  531. }
  532. //+-------------------------------------------------------------------------
  533. // Returns the next autocomplete string
  534. //--------------------------------------------------------------------------
  535. HRESULT CACThread::_Next(LPWSTR pszUrl, ULONG cchMax, ULONG* pulSortIndex)
  536. {
  537. ASSERT(pulSortIndex);
  538. HRESULT hr;
  539. // Use the new interface if we have it
  540. if (m_peac)
  541. {
  542. hr = m_peac->NextItem(pszUrl, cchMax, pulSortIndex);
  543. }
  544. // Fall back to the old IEnumString interface
  545. else
  546. {
  547. LPWSTR pszNext;
  548. ULONG ulFetched;
  549. hr = m_pes->Next(1, &pszNext, &ulFetched);
  550. if (S_OK == hr)
  551. {
  552. StrCpyN(pszUrl, pszNext, cchMax);
  553. if (pulSortIndex)
  554. {
  555. *pulSortIndex = 0;
  556. }
  557. CoTaskMemFree(pszNext);
  558. }
  559. }
  560. return hr;
  561. }
  562. //+-------------------------------------------------------------------------
  563. // Searches for items that match pszSearch.
  564. //--------------------------------------------------------------------------
  565. void CACThread::_Search
  566. (
  567. LPWSTR pszSearch, // String to search for (we must free this)
  568. DWORD dwOptions // ACO_* flags
  569. )
  570. {
  571. if (pszSearch)
  572. {
  573. TraceMsg(AC_GENERAL, "CACThread(BGThread)::_Search(pszSearch=0x%x)", pszSearch);
  574. // Save the search string in our thread data so it is still freed if this thread is killed
  575. m_pszSearch = pszSearch;
  576. // If we were passed a wildcard string, then everything matches
  577. BOOL fWildCard = ((pszSearch[0] == CH_WILDCARD) && (pszSearch[1] == L'\0'));
  578. // To avoid huge number of useless matches, avoid matches
  579. // to common prefixes
  580. BOOL fFilter = (dwOptions & ACO_FILTERPREFIXES) && MatchesSpecialPrefix(pszSearch);
  581. BOOL fAppendOnly = IsFlagSet(dwOptions, ACO_AUTOAPPEND) && IsFlagClear(dwOptions, ACO_AUTOSUGGEST);
  582. if (m_pes) // paranoia
  583. {
  584. // If this fails, the m_pes->Next() will likely do something
  585. // bad, so we will avoid it altogether.
  586. if (SUCCEEDED(m_pes->Reset()))
  587. {
  588. BOOL fStopped = FALSE;
  589. m_dwSearchStatus = 0;
  590. _DoExpand(pszSearch);
  591. int cchSearch = lstrlen(pszSearch);
  592. WCHAR szUrl[MAX_URL_STRING];
  593. ULONG ulSortIndex;
  594. while (!fStopped && IsFlagClear(m_dwSearchStatus, SRCH_LIMITREACHED) &&
  595. (_Next(szUrl, ARRAYSIZE(szUrl), &ulSortIndex) == S_OK))
  596. {
  597. //
  598. // First check for a simple match
  599. //
  600. if (fWildCard ||
  601. (StrCmpNI(szUrl, pszSearch, cchSearch) == 0) &&
  602. // Filter out matches to common prefixes
  603. (!fFilter || GetSpecialPrefixLen(szUrl) == 0))
  604. {
  605. _AddToList(szUrl, 0, ulSortIndex);
  606. }
  607. // If the dropdown is enabled, check for matches after common prefixes.
  608. if (!fAppendOnly)
  609. {
  610. //
  611. // Also check for a match if we skip the protocol. We
  612. // assume that szUrl has been cononicalized (protocol
  613. // in lower case).
  614. //
  615. LPCWSTR psz = szUrl;
  616. if (StrCmpN(szUrl, L"http://", 7) == 0)
  617. {
  618. psz += 7;
  619. }
  620. if (StrCmpN(szUrl, L"https://", 8) == 0 ||
  621. StrCmpN(szUrl, L"file:///", 8) == 0)
  622. {
  623. psz += 8;
  624. }
  625. if (psz != szUrl &&
  626. StrCmpNI(psz, pszSearch, cchSearch) == 0 &&
  627. // Filter out "www." prefixes
  628. (!fFilter || GetSpecialPrefixLen(psz) == 0))
  629. {
  630. _AddToList(szUrl, (int)(psz - szUrl), ulSortIndex);
  631. }
  632. //
  633. // Finally check for a match if we skip "www." after
  634. // the optional protocol
  635. //
  636. if (StrCmpN(psz, L"www.", 4) == 0 &&
  637. StrCmpNI(psz + 4, pszSearch, cchSearch) == 0)
  638. {
  639. _AddToList(szUrl, (int)(psz + 4 - szUrl), ulSortIndex);
  640. }
  641. }
  642. // Check to see if the search was canceled
  643. MSG msg;
  644. fStopped = PeekMessage(&msg, NULL, ACM_STOPSEARCH, ACM_STOPSEARCH, PM_NOREMOVE);
  645. #ifdef DEBUG
  646. fStopped = FALSE;
  647. if (fStopped)
  648. TraceMsg(AC_GENERAL, "AutoCompleteThread: Search TERMINATED");
  649. #endif
  650. }
  651. if (fStopped)
  652. {
  653. // Search aborted so free the results
  654. if (m_hdpa_list)
  655. {
  656. // clear the list
  657. CAutoComplete::_FreeDPAPtrs(m_hdpa_list);
  658. m_hdpa_list = NULL;
  659. }
  660. }
  661. else
  662. {
  663. //
  664. // Sort the results and remove duplicates
  665. //
  666. if (m_hdpa_list)
  667. {
  668. DPA_Sort(m_hdpa_list, _DpaCompare, 0);
  669. //
  670. // Perge duplicates.
  671. //
  672. for (int i = DPA_GetPtrCount(m_hdpa_list) - 1; i > 0; --i)
  673. {
  674. CACString& rStr1 = *(CACString*)DPA_GetPtr(m_hdpa_list, i-1);
  675. CACString& rStr2 = *(CACString*)DPA_GetPtr(m_hdpa_list, i);
  676. // Since URLs are case sensitive, we can't ignore case.
  677. if (rStr1.StrCmpI(rStr2) == 0)
  678. {
  679. // We have a match, so keep the longest string.
  680. if (rStr1.GetLength() > rStr2.GetLength())
  681. {
  682. // Use the smallest sort index
  683. if (rStr2.GetSortIndex() < rStr1.GetSortIndex())
  684. {
  685. rStr1.SetSortIndex(rStr2.GetSortIndex());
  686. }
  687. DPA_DeletePtr(m_hdpa_list, i);
  688. rStr2.Release();
  689. }
  690. else
  691. {
  692. // Use the smallest sort index
  693. if (rStr1.GetSortIndex() < rStr2.GetSortIndex())
  694. {
  695. rStr2.SetSortIndex(rStr1.GetSortIndex());
  696. }
  697. DPA_DeletePtr(m_hdpa_list, i-1);
  698. rStr1.Release();
  699. }
  700. }
  701. else
  702. {
  703. //
  704. // Special case: If this is a web site and the entries
  705. // are identical except one has an extra slash on the end
  706. // from a redirect, remove the redirected one.
  707. //
  708. int cch1 = rStr1.GetLengthToCompare();
  709. int cch2 = rStr2.GetLengthToCompare();
  710. int cchDiff = cch1 - cch2;
  711. if (
  712. // Length must differ by one
  713. (cchDiff == 1 || cchDiff == -1) &&
  714. // One string must have a terminating slash
  715. ((cch1 > 0 && rStr1[rStr1.GetLength() - 1] == L'/') ||
  716. (cch2 > 0 && rStr2[rStr2.GetLength() - 1] == L'/')) &&
  717. // Must be a web site
  718. ((StrCmpN(rStr1, L"http://", 7) == 0 || StrCmpN(rStr1, L"https://", 8) == 0) ||
  719. (StrCmpN(rStr2, L"http://", 7) == 0 || StrCmpN(rStr2, L"https://", 8) == 0)) &&
  720. // Must be identical up to the slash (ignoring prefix)
  721. StrCmpNI(rStr1.GetStrToCompare(), rStr2.GetStrToCompare(), (cchDiff > 0) ? cch2 : cch1) == 0)
  722. {
  723. // Remove the longer string with the extra slash
  724. if (cchDiff > 0)
  725. {
  726. // Use the smallest sort index
  727. if (rStr1.GetSortIndex() < rStr2.GetSortIndex())
  728. {
  729. rStr2.SetSortIndex(rStr1.GetSortIndex());
  730. }
  731. DPA_DeletePtr(m_hdpa_list, i-1);
  732. rStr1.Release();
  733. }
  734. else
  735. {
  736. // Use the smallest sort index
  737. if (rStr2.GetSortIndex() < rStr1.GetSortIndex())
  738. {
  739. rStr1.SetSortIndex(rStr2.GetSortIndex());
  740. }
  741. DPA_DeletePtr(m_hdpa_list, i);
  742. rStr2.Release();
  743. }
  744. }
  745. }
  746. }
  747. }
  748. // Pass the results to the foreground thread
  749. ENTERCRITICAL;
  750. if (m_pAutoComp)
  751. {
  752. HWND hwndEdit = m_pAutoComp->m_hwndEdit;
  753. UINT uMsgSearchComplete = m_pAutoComp->m_uMsgSearchComplete;
  754. LEAVECRITICAL;
  755. // Unix loses keys if we post the message, so we send the message
  756. // outside our critical section
  757. SendMessage(hwndEdit, uMsgSearchComplete, m_dwSearchStatus, (LPARAM)m_hdpa_list);
  758. }
  759. else
  760. {
  761. LEAVECRITICAL;
  762. // We've been orphaned, so free the list and bail
  763. CAutoComplete::_FreeDPAPtrs(m_hdpa_list);
  764. }
  765. // The foreground thread owns the list now
  766. m_hdpa_list = NULL;
  767. }
  768. }
  769. else
  770. {
  771. ASSERT(0); // m_pes->Reset Failed!!
  772. }
  773. }
  774. // We must free the search string
  775. m_pszSearch = NULL;
  776. // Note if the thread is killed here, we leak the string
  777. // but at least we will not try to free it twice (which is worse)
  778. // because we nulled m_pszSearch first.
  779. LocalFree(pszSearch);
  780. }
  781. }
  782. //+-------------------------------------------------------------------------
  783. // Used to sort items alphabetically
  784. //--------------------------------------------------------------------------
  785. int CALLBACK CACThread::_DpaCompare(void *p1, void *p2, LPARAM lParam)
  786. {
  787. CACString* ps1 = (CACString*)p1;
  788. CACString* ps2 = (CACString*)p2;
  789. return ps1->StrCmpI(*ps2);
  790. }
  791. //+-------------------------------------------------------------------------
  792. // Adds a string to our HDPA. Returns TRUE is successful.
  793. //--------------------------------------------------------------------------
  794. BOOL CACThread::_AddToList
  795. (
  796. LPTSTR pszUrl, // string to add
  797. int cchMatch, // offset into string where the match occurred
  798. ULONG ulSortIndex // controls order of items displayed
  799. )
  800. {
  801. TraceMsg(AC_GENERAL, "CACThread(BGThread)::_AddToList(pszUrl = %s)",
  802. (pszUrl ? pszUrl : TEXT("(null)")));
  803. BOOL fRet = TRUE;
  804. //
  805. // Create a new list if necessary.
  806. //
  807. if (!m_hdpa_list)
  808. {
  809. m_hdpa_list = DPA_Create(AC_LIST_GROWTH_CONST);
  810. }
  811. if (m_hdpa_list && DPA_GetPtrCount(m_hdpa_list) < AC_GIVEUP_COUNT)
  812. {
  813. CACString* pStr = CreateACString(pszUrl, cchMatch, ulSortIndex);
  814. if (pStr)
  815. {
  816. if (DPA_AppendPtr(m_hdpa_list, pStr) == -1)
  817. {
  818. pStr->Release();
  819. m_dwSearchStatus |= SRCH_LIMITREACHED;
  820. fRet = FALSE;
  821. }
  822. // If we have a nonzero sort index, the forground thread will need
  823. // to use it to order the results
  824. else if (ulSortIndex)
  825. {
  826. m_dwSearchStatus |= SRCH_USESORTINDEX;
  827. }
  828. }
  829. }
  830. else
  831. {
  832. m_dwSearchStatus |= SRCH_LIMITREACHED;
  833. fRet = FALSE;
  834. }
  835. return fRet;
  836. }
  837. //+-------------------------------------------------------------------------
  838. // This function will attempt to use the autocomplete list to bind to a
  839. // location in the Shell Name Space. If that succeeds, the AutoComplete List
  840. // will then contain entries which are the display names in that ISF.
  841. //--------------------------------------------------------------------------
  842. void CACThread::_DoExpand(LPCWSTR pszSearch)
  843. {
  844. LPCWSTR psz;
  845. if (!m_pacl)
  846. {
  847. //
  848. // Doesn't support IAutoComplete, doesn't have Expand method.
  849. //
  850. return;
  851. }
  852. if (*pszSearch == 0)
  853. {
  854. //
  855. // No string means no expansion necessary.
  856. //
  857. return;
  858. }
  859. //
  860. // psz points to last character.
  861. //
  862. psz = pszSearch + lstrlen(pszSearch);
  863. psz = CharPrev(pszSearch, psz);
  864. //
  865. // Search backwards for an expand break character.
  866. //
  867. while (psz != pszSearch && *psz != TEXT('/') && *psz != TEXT('\\'))
  868. {
  869. psz = CharPrev(pszSearch, psz);
  870. }
  871. if (*psz == TEXT('/') || *psz == TEXT('\\'))
  872. {
  873. SHSTR ss;
  874. psz++;
  875. if (SUCCEEDED(ss.SetStr(pszSearch)))
  876. {
  877. //
  878. // Trim ss so that it contains everything up to the last
  879. // expand break character.
  880. //
  881. LPTSTR pszTemp = ss.GetInplaceStr();
  882. pszTemp[psz - pszSearch] = TEXT('\0');
  883. //
  884. // Call expand on the string.
  885. //
  886. m_pacl->Expand(ss);
  887. }
  888. }
  889. }