535c1af09d0cdd6314475947eae3f79abcc132fe
[reactos.git] / dll / win32 / browseui / shellfind / CFindFolder.cpp
1 /*
2 * PROJECT: ReactOS Search Shell Extension
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Search results folder
5 * COPYRIGHT: Copyright 2019 Brock Mammen
6 */
7
8 #include "CFindFolder.h"
9
10 WINE_DEFAULT_DEBUG_CHANNEL(shellfind);
11
12 // FIXME: Remove this declaration after the function has been fully implemented
13 EXTERN_C HRESULT
14 WINAPI
15 SHOpenFolderAndSelectItems(LPITEMIDLIST pidlFolder,
16 UINT cidl,
17 PCUITEMID_CHILD_ARRAY apidl,
18 DWORD dwFlags);
19
20 struct FolderViewColumns
21 {
22 LPCWSTR wzColumnName;
23 DWORD dwDefaultState;
24 int fmt;
25 int cxChar;
26 };
27
28 static FolderViewColumns g_ColumnDefs[] =
29 {
30 {L"Name", SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30},
31 {L"In Folder", SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30},
32 {L"Relevance", SHCOLSTATE_TYPE_STR, LVCFMT_LEFT, 0}
33 };
34
35 CFindFolder::CFindFolder() :
36 m_hStopEvent(NULL)
37 {
38 }
39
40 static LPITEMIDLIST _ILCreate(LPCWSTR lpszPath)
41 {
42 CComHeapPtr<ITEMIDLIST> lpFSPidl(ILCreateFromPathW(lpszPath));
43 if (!(LPITEMIDLIST)lpFSPidl)
44 {
45 ERR("Failed to create pidl from path\n");
46 return 0;
47 }
48 LPITEMIDLIST lpLastFSPidl = ILFindLastID(lpFSPidl);
49
50 int pathLen = (wcslen(lpszPath) + 1) * sizeof(WCHAR);
51 int cbData = sizeof(WORD) + pathLen + lpLastFSPidl->mkid.cb;
52 LPITEMIDLIST pidl = (LPITEMIDLIST) SHAlloc(cbData + sizeof(WORD));
53 if (!pidl)
54 return NULL;
55
56 LPBYTE p = (LPBYTE) pidl;
57 *((WORD *) p) = cbData;
58 p += sizeof(WORD);
59
60 memcpy(p, lpszPath, pathLen);
61 p += pathLen;
62
63 memcpy(p, lpLastFSPidl, lpLastFSPidl->mkid.cb);
64 p += lpLastFSPidl->mkid.cb;
65
66 *((WORD *) p) = 0;
67
68 return pidl;
69 }
70
71 static LPCWSTR _ILGetPath(LPCITEMIDLIST pidl)
72 {
73 if (!pidl || !pidl->mkid.cb)
74 return NULL;
75 return (LPCWSTR) pidl->mkid.abID;
76 }
77
78 static LPCITEMIDLIST _ILGetFSPidl(LPCITEMIDLIST pidl)
79 {
80 if (!pidl || !pidl->mkid.cb)
81 return pidl;
82 return (LPCITEMIDLIST) ((LPBYTE) pidl->mkid.abID
83 + ((wcslen((LPCWSTR) pidl->mkid.abID) + 1) * sizeof(WCHAR)));
84 }
85
86 struct _SearchData
87 {
88 HWND hwnd;
89 HANDLE hStopEvent;
90 SearchStart *pSearchParams;
91 };
92
93 static LPCSTR WINAPI StrStrNA(LPCSTR lpFirst, LPCSTR lpSrch, UINT cchMax)
94 {
95 UINT i;
96 int len;
97
98 if (!lpFirst || !lpSrch || !*lpSrch || !cchMax)
99 return NULL;
100
101 len = strlen(lpSrch);
102
103 for (i = cchMax; *lpFirst && (i > 0); i--, lpFirst++)
104 {
105 if (!strncmp(lpFirst, lpSrch, len))
106 return (LPCSTR)lpFirst;
107 }
108
109 return NULL;
110 }
111
112 static UINT SearchFile(LPCWSTR lpFilePath, _SearchData *pSearchData)
113 {
114 HANDLE hFile = CreateFileW(lpFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
115 if (hFile == INVALID_HANDLE_VALUE)
116 return 0;
117
118 DWORD size = GetFileSize(hFile, NULL);
119 HANDLE hFileMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
120 CloseHandle(hFile);
121 if (hFileMap == INVALID_HANDLE_VALUE)
122 return 0;
123
124 LPBYTE lpFileContent = (LPBYTE) MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
125 CloseHandle(hFileMap);
126 if (!lpFileContent)
127 return 0;
128
129 UINT uMatches = 0;
130 if ((size >= 2) && (lpFileContent[0] == 0xFF) && (lpFileContent[1] == 0xFE))
131 {
132 // UTF16 LE
133 LPCWSTR lpSearchPos = (LPCWSTR) lpFileContent;
134 DWORD dwCharsRemaining = size / sizeof(WCHAR);
135 const LPCWSTR lpSearchEnd = (LPCWSTR) lpFileContent + dwCharsRemaining;
136 const LPCWSTR lpszQuery = pSearchData->pSearchParams->szQuery;
137 const size_t queryLen = wcslen(lpszQuery);
138 while ((lpSearchPos = StrStrNW(lpSearchPos, lpszQuery, dwCharsRemaining))
139 && lpSearchPos < lpSearchEnd)
140 {
141 uMatches++;
142 lpSearchPos += queryLen;
143 dwCharsRemaining -= queryLen;
144 }
145 }
146 else
147 {
148 DWORD len = WideCharToMultiByte(CP_ACP, 0, pSearchData->pSearchParams->szQuery, -1, NULL, 0, NULL, NULL);
149 const LPSTR lpszQuery = new CHAR[len];
150 WideCharToMultiByte(CP_ACP, 0, pSearchData->pSearchParams->szQuery, -1, lpszQuery, len, NULL, NULL);
151 LPCSTR lpSearchPos = (LPCSTR) lpFileContent;
152 DWORD dwCharsRemaining = size;
153 const LPCSTR lpSearchEnd = (LPCSTR) lpFileContent + dwCharsRemaining;
154 const size_t queryLen = len;
155 while ((lpSearchPos = StrStrNA(lpSearchPos, lpszQuery, dwCharsRemaining))
156 && lpSearchPos < lpSearchEnd)
157 {
158 uMatches++;
159 lpSearchPos += queryLen;
160 dwCharsRemaining -= queryLen;
161 }
162 }
163
164 UnmapViewOfFile(lpFileContent);
165
166 return uMatches;
167 }
168
169 static VOID RecursiveFind(LPCWSTR lpPath, _SearchData *pSearchData)
170 {
171 if (WaitForSingleObject(pSearchData->hStopEvent, 0) != WAIT_TIMEOUT)
172 return;
173
174 WCHAR szPath[MAX_PATH];
175 WIN32_FIND_DATAW FindData;
176 HANDLE hFindFile;
177 BOOL bMoreFiles = TRUE;
178
179 PathCombineW(szPath, lpPath, L"*.*");
180
181 for (hFindFile = FindFirstFileW(szPath, &FindData);
182 bMoreFiles && hFindFile != INVALID_HANDLE_VALUE;
183 bMoreFiles = FindNextFileW(hFindFile, &FindData))
184 {
185 if (!wcscmp(FindData.cFileName, L".") || !wcscmp(FindData.cFileName, L".."))
186 continue;
187
188 PathCombineW(szPath, lpPath, FindData.cFileName);
189
190 if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
191 {
192 CStringW* status = new CStringW();
193 status->Format(L"Searching '%s'", FindData.cFileName);
194 PostMessageW(pSearchData->hwnd, WM_SEARCH_UPDATE_STATUS, 0, (LPARAM) status);
195
196 RecursiveFind(szPath, pSearchData);
197 }
198 else if (pSearchData->szFileName.IsEmpty() || PathMatchSpecW(FindData.cFileName, pSearchData->szFileName))
199 {
200 DbgPrint("Searching file: '%S'\n", szPath);
201 UINT uMatches = SearchFile(szPath, pSearchData);
202 if (uMatches)
203 {
204 ::PostMessageW(pSearchData->hwnd, WM_SEARCH_ADD_RESULT, 0, (LPARAM) StrDupW(szPath));
205 }
206 }
207 }
208
209 if (hFindFile != INVALID_HANDLE_VALUE)
210 FindClose(hFindFile);
211 }
212
213 static DWORD WINAPI _SearchThreadProc(LPVOID lpParameter)
214 {
215 _SearchData *data = static_cast<_SearchData*>(lpParameter);
216
217 SearchStart* params = (SearchStart *) data->pSearchParams;
218
219 RecursiveFind(params->szPath, data);
220
221 SHFree(params);
222 SHFree(lpParameter);
223
224 return 0;
225 }
226
227 LRESULT CFindFolder::StartSearch(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
228 {
229 if (!lParam)
230 return 0;
231
232 // Clear all previous search results
233 UINT uItemIndex;
234 m_shellFolderView->RemoveObject(NULL, &uItemIndex);
235
236 _SearchData* pSearchData = new _SearchData();
237 pSearchData->pFindFolder = this;
238 pSearchData->hwnd = m_hWnd;
239 if (m_hStopEvent)
240 SetEvent(m_hStopEvent);
241 pSearchData->hStopEvent = m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
242 pSearchData->pSearchParams = (SearchStart *) lParam;
243
244 if (!SHCreateThread(_SearchThreadProc, pSearchData, NULL, NULL))
245 {
246 SHFree(pSearchData->pSearchParams);
247 SHFree(pSearchData);
248 return HRESULT_FROM_WIN32(GetLastError());
249 }
250
251 return S_OK;
252 }
253
254 LRESULT CFindFolder::AddResult(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
255 {
256 if (!lParam)
257 return 0;
258
259 CComHeapPtr<WCHAR> lpPath((LPWSTR) lParam);
260
261 CComHeapPtr<ITEMIDLIST> lpSearchPidl(_ILCreate(lpPath));
262 if (lpSearchPidl)
263 {
264 UINT uItemIndex;
265 m_shellFolderView->AddObject(lpSearchPidl, &uItemIndex);
266 }
267
268 return 0;
269 }
270
271 LRESULT CFindFolder::UpdateStatus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
272 {
273 CStringW *status = (CStringW *) lParam;
274 if (m_shellBrowser)
275 {
276 m_shellBrowser->SetStatusTextSB(status->GetBuffer());
277 }
278 delete status;
279
280 return S_OK;
281 }
282
283 // *** IShellFolder2 methods ***
284 STDMETHODIMP CFindFolder::GetDefaultSearchGUID(GUID *pguid)
285 {
286 UNIMPLEMENTED;
287 return E_NOTIMPL;
288 }
289
290 STDMETHODIMP CFindFolder::EnumSearches(IEnumExtraSearch **ppenum)
291 {
292 UNIMPLEMENTED;
293 return E_NOTIMPL;
294 }
295
296 STDMETHODIMP CFindFolder::GetDefaultColumn(DWORD, ULONG *pSort, ULONG *pDisplay)
297 {
298 if (pSort)
299 *pSort = 0;
300 if (pDisplay)
301 *pDisplay = 0;
302 return S_OK;
303 }
304
305 STDMETHODIMP CFindFolder::GetDefaultColumnState(UINT iColumn, DWORD *pcsFlags)
306 {
307 if (!pcsFlags)
308 return E_INVALIDARG;
309 if (iColumn >= _countof(g_ColumnDefs))
310 return m_pisfInner->GetDefaultColumnState(iColumn - _countof(g_ColumnDefs) + 1, pcsFlags);
311 *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState;
312 return S_OK;
313 }
314
315 STDMETHODIMP CFindFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
316 {
317 return m_pisfInner->GetDetailsEx(pidl, pscid, pv);
318 }
319
320 STDMETHODIMP CFindFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *pDetails)
321 {
322 if (iColumn >= _countof(g_ColumnDefs))
323 return m_pisfInner->GetDetailsOf(_ILGetFSPidl(pidl), iColumn - _countof(g_ColumnDefs) + 1, pDetails);
324
325 pDetails->cxChar = g_ColumnDefs[iColumn].cxChar;
326 pDetails->fmt = g_ColumnDefs[iColumn].fmt;
327
328 if (!pidl)
329 return SHSetStrRet(&pDetails->str, g_ColumnDefs[iColumn].wzColumnName);
330
331 if (iColumn == 1)
332 {
333 WCHAR path[MAX_PATH];
334 wcscpy(path, _ILGetPath(pidl));
335 PathRemoveFileSpecW(path);
336 return SHSetStrRet(&pDetails->str, path);
337 }
338
339 return GetDisplayNameOf(pidl, SHGDN_NORMAL, &pDetails->str);
340 }
341
342 STDMETHODIMP CFindFolder::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
343 {
344 UNIMPLEMENTED;
345 return E_NOTIMPL;
346 }
347
348 // *** IShellFolder methods ***
349 STDMETHODIMP CFindFolder::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, ULONG *pchEaten,
350 PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes)
351 {
352 UNIMPLEMENTED;
353 return E_NOTIMPL;
354 }
355
356 STDMETHODIMP CFindFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
357 {
358 *ppEnumIDList = NULL;
359 return S_FALSE;
360 }
361
362 STDMETHODIMP CFindFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
363 {
364 UNIMPLEMENTED;
365 return E_NOTIMPL;
366 }
367
368 STDMETHODIMP CFindFolder::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
369 {
370 UNIMPLEMENTED;
371 return E_NOTIMPL;
372 }
373
374 STDMETHODIMP CFindFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
375 {
376 return m_pisfInner->CompareIDs(lParam, _ILGetFSPidl(pidl1), _ILGetFSPidl(pidl2));
377 }
378
379 STDMETHODIMP CFindFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut)
380 {
381 if (riid == IID_IShellView)
382 {
383 SFV_CREATE sfvparams = {};
384 sfvparams.cbSize = sizeof(SFV_CREATE);
385 sfvparams.pshf = this;
386 sfvparams.psfvcb = this;
387 HRESULT hr = SHCreateShellFolderView(&sfvparams, (IShellView **) ppvOut);
388 if (FAILED_UNEXPECTEDLY(hr))
389 {
390 return hr;
391 }
392
393 return ((IShellView * ) * ppvOut)->QueryInterface(IID_PPV_ARG(IShellFolderView, &m_shellFolderView));
394 }
395 return E_NOINTERFACE;
396 }
397
398 STDMETHODIMP CFindFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut)
399 {
400 CComHeapPtr<PCITEMID_CHILD> aFSPidl;
401 aFSPidl.Allocate(cidl);
402 for (UINT i = 0; i < cidl; i++)
403 {
404 aFSPidl[i] = _ILGetFSPidl(apidl[i]);
405 }
406
407 return m_pisfInner->GetAttributesOf(cidl, aFSPidl, rgfInOut);
408 }
409
410 STDMETHODIMP CFindFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid,
411 UINT *prgfInOut, LPVOID *ppvOut)
412 {
413 if (riid == IID_IDataObject && cidl == 1)
414 {
415 WCHAR path[MAX_PATH];
416 wcscpy(path, (LPCWSTR) apidl[0]->mkid.abID);
417 PathRemoveFileSpecW(path);
418 CComHeapPtr<ITEMIDLIST> rootPidl(ILCreateFromPathW(path));
419 if (!rootPidl)
420 return E_OUTOFMEMORY;
421 PCITEMID_CHILD aFSPidl[1];
422 aFSPidl[0] = _ILGetFSPidl(apidl[0]);
423 return IDataObject_Constructor(hwndOwner, rootPidl, aFSPidl, cidl, (IDataObject **) ppvOut);
424 }
425
426 if (cidl <= 0)
427 {
428 return m_pisfInner->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, ppvOut);
429 }
430
431 PCITEMID_CHILD *aFSPidl = new PCITEMID_CHILD[cidl];
432 for (UINT i = 0; i < cidl; i++)
433 {
434 aFSPidl[i] = _ILGetFSPidl(apidl[i]);
435 }
436
437 if (riid == IID_IContextMenu)
438 {
439 HKEY hKeys[16];
440 UINT cKeys = 0;
441 AddFSClassKeysToArray(aFSPidl[0], hKeys, &cKeys);
442
443 DEFCONTEXTMENU dcm;
444 dcm.hwnd = hwndOwner;
445 dcm.pcmcb = this;
446 dcm.pidlFolder = m_pidl;
447 dcm.psf = this;
448 dcm.cidl = cidl;
449 dcm.apidl = apidl;
450 dcm.cKeys = cKeys;
451 dcm.aKeys = hKeys;
452 dcm.punkAssociationInfo = NULL;
453 HRESULT hr = SHCreateDefaultContextMenu(&dcm, riid, ppvOut);
454 delete[] aFSPidl;
455
456 return hr;
457 }
458
459 HRESULT hr = m_pisfInner->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, ppvOut);
460 delete[] aFSPidl;
461
462 return hr;
463 }
464
465 STDMETHODIMP CFindFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName)
466 {
467 return m_pisfInner->GetDisplayNameOf(_ILGetFSPidl(pidl), dwFlags, pName);
468 }
469
470 STDMETHODIMP CFindFolder::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags,
471 PITEMID_CHILD *pPidlOut)
472 {
473 UNIMPLEMENTED;
474 return E_NOTIMPL;
475 }
476
477 //// *** IShellFolderViewCB method ***
478 STDMETHODIMP CFindFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam)
479 {
480 switch (uMsg)
481 {
482 case SFVM_DEFVIEWMODE:
483 {
484 FOLDERVIEWMODE *pViewMode = (FOLDERVIEWMODE *) lParam;
485 *pViewMode = FVM_DETAILS;
486 return S_OK;
487 }
488 case SFVM_WINDOWCREATED:
489 {
490 SubclassWindow((HWND) wParam);
491
492 CComPtr<IServiceProvider> pServiceProvider;
493 HRESULT hr = m_shellFolderView->QueryInterface(IID_PPV_ARG(IServiceProvider, &pServiceProvider));
494 if (FAILED_UNEXPECTEDLY(hr))
495 {
496 return hr;
497 }
498 return pServiceProvider->QueryService(SID_SShellBrowser, IID_PPV_ARG(IShellBrowser, &m_shellBrowser));
499 }
500 }
501 return E_NOTIMPL;
502 }
503
504 //// *** IContextMenuCB method ***
505 STDMETHODIMP CFindFolder::CallBack(IShellFolder *psf, HWND hwndOwner, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
506 {
507 switch (uMsg)
508 {
509 case DFM_MERGECONTEXTMENU:
510 {
511 QCMINFO *pqcminfo = (QCMINFO *) lParam;
512 _InsertMenuItemW(pqcminfo->hmenu, pqcminfo->indexMenu++, TRUE, pqcminfo->idCmdFirst++, MFT_SEPARATOR, NULL, 0);
513 _InsertMenuItemW(pqcminfo->hmenu, pqcminfo->indexMenu++, TRUE, pqcminfo->idCmdFirst++, MFT_STRING, L"Open Containing Folder", MFS_ENABLED);
514 _InsertMenuItemW(pqcminfo->hmenu, pqcminfo->indexMenu++, TRUE, pqcminfo->idCmdFirst++, MFT_SEPARATOR, NULL, 0);
515 return S_OK;
516 }
517 case DFM_INVOKECOMMAND:
518 case DFM_INVOKECOMMANDEX:
519 {
520 if (wParam != 1)
521 break;
522
523 PCUITEMID_CHILD *apidl;
524 UINT cidl;
525 HRESULT hr = m_shellFolderView->GetSelectedObjects(&apidl, &cidl);
526 if (FAILED_UNEXPECTEDLY(hr))
527 return hr;
528
529 for (UINT i = 0; i < cidl; i++)
530 {
531 CComHeapPtr<ITEMIDLIST> pidl;
532 DWORD attrs = 0;
533 hr = SHILCreateFromPathW((LPCWSTR) apidl[i]->mkid.abID, &pidl, &attrs);
534 if (SUCCEEDED(hr))
535 {
536 SHOpenFolderAndSelectItems(NULL, 1, &pidl, 0);
537 }
538 }
539
540 return S_OK;
541 }
542 case DFM_GETDEFSTATICID:
543 return S_FALSE;
544 }
545 return Shell_DefaultContextMenuCallBack(m_pisfInner, pdtobj);
546 }
547
548 //// *** IPersistFolder2 methods ***
549 STDMETHODIMP CFindFolder::GetCurFolder(LPITEMIDLIST *pidl)
550 {
551 *pidl = ILClone(m_pidl);
552 return S_OK;
553 }
554
555 // *** IPersistFolder methods ***
556 STDMETHODIMP CFindFolder::Initialize(LPCITEMIDLIST pidl)
557 {
558 m_pidl = ILClone(pidl);
559 if (!m_pidl)
560 return E_OUTOFMEMORY;
561
562 return SHELL32_CoCreateInitSF(m_pidl,
563 NULL,
564 NULL,
565 &CLSID_ShellFSFolder,
566 IID_PPV_ARG(IShellFolder2, &m_pisfInner));
567 }
568
569 // *** IPersist methods ***
570 STDMETHODIMP CFindFolder::GetClassID(CLSID *pClassId)
571 {
572 if (pClassId == NULL)
573 return E_INVALIDARG;
574 memcpy(pClassId, &CLSID_FindFolder, sizeof(CLSID));
575 return S_OK;
576 }