|
|
#include "shellprv.h"
#include "ids.h"
#include "findhlp.h"
#include "fstreex.h"
#include "findfilter.h"
#include "prop.h"
#include "filtgrep.h"
#include "shstr.h"
#include "mtpt.h"
#include "idltree.h"
#include "enumidlist.h"
// Can't put this in varutil.cpp since a delay-load thunk for VariantTimeToDosDateTime
// pulls in the floating point init code, which pulls in _wWinMainCRTStartup which
// requires a _wWinMain which shlwapi does not have
//
STDAPI VariantToDosDateTime(VARIANT varIn, WORD *pwDate, WORD *pwTime) { VARIANT varResult = {0}; HRESULT hr = VariantChangeType(&varResult, &varIn, 0, VT_DATE); if (SUCCEEDED(hr)) { VariantTimeToDosDateTime(varResult.date, pwDate, pwTime); } return hr; }
STDAPI InitVariantFromDosDateTime(VARIANT *pvar, WORD wDate, WORD wTime) { pvar->vt = VT_DATE; return DosDateTimeToVariantTime(wDate, wTime, &pvar->date) ? S_OK : S_FALSE; }
// {DBEC1000-6AB8-11d1-B758-00A0C90564FE}
const IID IID_IFindFilter = {0xdbec1000, 0x6ab8, 0x11d1, {0xb7, 0x58, 0x0, 0xa0, 0xc9, 0x5, 0x64, 0xfe}};
// constants to define types of date we are searching on
#define DFF_DATE_ALL (IDD_MDATE_ALL-IDD_MDATE_ALL)
#define DFF_DATE_DAYS (IDD_MDATE_DAYS-IDD_MDATE_ALL)
#define DFF_DATE_MONTHS (IDD_MDATE_MONTHS-IDD_MDATE_ALL)
#define DFF_DATE_BETWEEN (IDD_MDATE_BETWEEN-IDD_MDATE_ALL)
#define DFF_DATE_RANGEMASK 0x00ff
// Define new criteria to be saved in file...
#define DFSC_SEARCHFOR 0x5000
#define DFSLI_VER 0
#define DFSLI_TYPE_PIDL 0 // Pidl is streamed after this
#define DFSLI_TYPE_STRING 1 // cb follows this for length then string...
// Document folders and children - Warning we assume the order of items after Document Folders
#define DFSLI_TYPE_DOCUMENTFOLDERS 0x10
#define DFSLI_TYPE_DESKTOP 0x11
#define DFSLI_TYPE_PERSONAL 0x12
// My computer and children...
#define DFSLI_TYPE_MYCOMPUTER 0x20
#define DFSLI_TYPE_LOCALDRIVES 0x21
#define DFPAGE_INIT 0x0001 /* This page has been initialized */
#define DFPAGE_CHANGE 0x0002 /* The user has modified the page */
#define SFGAO_FS_SEARCH (SFGAO_FILESYSANCESTOR | SFGAO_FOLDER)
// Use same enum and string table between updatefield and getting the constraints
// back out...
typedef enum { CDFFUFE_IndexedSearch = 0, CDFFUFE_LookIn, CDFFUFE_IncludeSubFolders, CDFFUFE_Named, CDFFUFE_ContainingText, CDFFUFE_FileType, CDFFUFE_WhichDate, CDFFUFE_DateLE, CDFFUFE_DateGE, CDFFUFE_DateNDays, CDFFUFE_DateNMonths, CDFFUFE_SizeLE, CDFFUFE_SizeGE, CDFFUFE_TextCaseSen, CDFFUFE_TextReg, CDFFUFE_SearchSlowFiles, CDFFUFE_QueryDialect, CDFFUFE_WarningFlags, CDFFUFE_StartItem, CDFFUFE_SearchSystemDirs, CDFFUFE_SearchHidden, } CDFFUFE;
static const struct { LPCWSTR pwszField; int cdffufe; } s_cdffuf[] = // Warning: index of fields is used below in case...
{ {L"IndexedSearch", CDFFUFE_IndexedSearch}, {L"LookIn", CDFFUFE_LookIn}, // VARIANT: pidl, string or IEnumIDList object
{L"IncludeSubFolders", CDFFUFE_IncludeSubFolders}, {L"Named", CDFFUFE_Named}, {L"ContainingText", CDFFUFE_ContainingText}, {L"FileType", CDFFUFE_FileType}, {L"WhichDate", CDFFUFE_WhichDate}, {L"DateLE", CDFFUFE_DateLE}, {L"DateGE", CDFFUFE_DateGE}, {L"DateNDays", CDFFUFE_DateNDays}, {L"DateNMonths", CDFFUFE_DateNMonths}, {L"SizeLE", CDFFUFE_SizeLE}, {L"SizeGE", CDFFUFE_SizeGE}, {L"CaseSensitive", CDFFUFE_TextCaseSen}, {L"RegularExpressions", CDFFUFE_TextReg}, {L"SearchSlowFiles", CDFFUFE_SearchSlowFiles}, {L"QueryDialect", CDFFUFE_QueryDialect}, {L"WarningFlags", CDFFUFE_WarningFlags}, /*DFW_xxx bits*/ {L"StartItem", CDFFUFE_LookIn}, // VARIANT: pidl, string or IEnumIDList object
{L"SearchSystemDirs", CDFFUFE_SearchSystemDirs}, {L"SearchHidden", CDFFUFE_SearchHidden}, };
// internal support functions
STDAPI_(BOOL) SetupWildCardingOnFileSpec(LPTSTR pszSpecIn, LPTSTR *ppszSpecOut);
// data filter object
class CFindFilter : public IFindFilter { public: CFindFilter();
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release();
// IFindFilter
STDMETHODIMP GetStatusMessageIndex(UINT uContext, UINT *puMsgIndex); STDMETHODIMP GetFolderMergeMenuIndex(UINT *puBGMainMergeMenu, UINT *puBGPopupMergeMenu); STDMETHODIMP FFilterChanged(); STDMETHODIMP GenerateTitle(LPTSTR *ppszTile, BOOL fFileName); STDMETHODIMP PrepareToEnumObjects(HWND hwnd, DWORD * pdwFlags); STDMETHODIMP ClearSearchCriteria(); STDMETHODIMP EnumObjects(IShellFolder *psf, LPCITEMIDLIST pidlStart, DWORD grfFlags, int iColSort, LPTSTR pszProgressText, IRowsetWatchNotify *prwn, IFindEnum **ppdfenum); STDMETHODIMP GetColumnsFolder(IShellFolder2 **ppsf); STDMETHODIMP_(BOOL) MatchFilter(IShellFolder *psf, LPCITEMIDLIST pidl); STDMETHODIMP SaveCriteria(IStream * pstm, WORD fCharType); STDMETHODIMP RestoreCriteria(IStream * pstm, int cCriteria, WORD fCharType); STDMETHODIMP DeclareFSNotifyInterest(HWND hwnd, UINT uMsg); STDMETHODIMP GetColSaveStream(WPARAM wParam, LPSTREAM *ppstm); STDMETHODIMP GenerateQueryRestrictions(LPWSTR *ppwszQuery, DWORD *pdwGQRFlags); STDMETHODIMP ReleaseQuery(); STDMETHODIMP UpdateField(LPCWSTR pszField, VARIANT vValue); STDMETHODIMP ResetFieldsToDefaults(); STDMETHODIMP GetItemContextMenu(HWND hwndOwner, IFindFolder* pdfFolder, IContextMenu** ppcm); STDMETHODIMP GetDefaultSearchGUID(IShellFolder2 *psf2, LPGUID lpGuid); STDMETHODIMP EnumSearches(IShellFolder2 *psf2, LPENUMEXTRASEARCH *ppenum); STDMETHODIMP GetSearchFolderClassId(LPGUID lpGuid); STDMETHODIMP GetNextConstraint(VARIANT_BOOL fReset, BSTR *pName, VARIANT *pValue, VARIANT_BOOL *pfFound); STDMETHODIMP GetQueryLanguageDialect(ULONG * pulDialect); STDMETHODIMP GetWarningFlags(DWORD *pdwWarningFlags);
STDMETHODIMP InitSelf(void); STDMETHODIMP_(BOOL) TopLevelOnly() const { return _fTopLevelOnly; }
private: ~CFindFilter(); HRESULT _GetDetailsFolder(); void _GenerateQuery(LPWSTR pwszQuery, DWORD *pcchQuery); void _UpdateTypeField(const VARIANT *pvar); static int _SaveCriteriaItem(IStream * pstm, WORD wNum, LPCTSTR psz, WORD fCharType); DWORD _QueryDosDate(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, WORD wDate, BOOL bBefore); HRESULT _GetPropertyUI(IPropertyUI **pppui); DWORD _CIQuerySize(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, ULONGLONG ullSize, int iSizeType); DWORD _CIQueryFilePatterns(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, LPCWSTR pszFilePatterns); DWORD _CIQueryTextPatterns(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, LPWSTR pszText, BOOL bTextReg); DWORD _CIQueryShellSettings(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent); DWORD _CIQueryIndex(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, LPWSTR pszText); DWORD _AddToQuery(LPWSTR *ppszBuf, DWORD *pcchBuf, LPWSTR pszAdd); WORD _GetTodaysDosDateMinusNMonths(int nMonths); WORD _GetTodaysDosDateMinusNDays(int nDays); HRESULT _ScopeEnumerator(IEnumIDList **ppenum); void _ResetRoots();
LONG _cRef; IFindEnum *_penumAsync; // Added support for Query results to be async...
// Data associated with the file name.
LPTSTR _pszFileSpec; // $$ the one we do compares with
LPTSTR _pszSpecs; // same as pszFileSpec but with '\0's for ';'s
LPTSTR * _apszFileSpecs; // pointers into pszSpecs for each token
int _cFileSpecs; // count of specs
TCHAR _szPath[MAX_URL_STRING]; // Location of where to start search from
TCHAR _szUserInputFileSpec[MAX_PATH]; // File pattern.
TCHAR _szText[128]; // Limit text to max editable size
BOOL _fTopLevelOnly; // Search on top level only?
BOOL _fSearchHidden; // $$ Should we show all files?
BOOL _fFilterChanged; // Something in the filter changed.
BOOL _fWeRestoredSomeCriteria; // We need to initilize the pages...
BOOL _fANDSearch; // Perform search using AND vs OR?
// Fields associated with the file type
BOOL _fTypeChanged; // Type changed;
int _iType; // Index of the type.
TCHAR _szTypeName[80]; // The display name for type
SHSTR _strTypeFilePatterns;// $$ The file patterns associated with type
LPTSTR _pszIndexedSearch; // what to search for... (Maybe larger than MAX_PATH because it's a list of paths.
ULONG _ulQueryDialect; // ISQLANG_V1 or ISQLANG_V2
DWORD _dwWarningFlags; // Warning bits (DFW_xxx).
CFilterGrep _filtgrep;
int _iSizeType; // $$ What type of size 0 - none, 1 > 2 <
ULONGLONG _ullSize; // $$ Size comparison
WORD _wDateType; // $$ 0 - none, 1 days before, 2 months before...
WORD _wDateValue; // (Num of months or days)
WORD _dateModifiedBefore; // $$
WORD _dateModifiedAfter; // $$
BOOL _fFoldersOnly; // $$ Are we searching for folders?
BOOL _fTextCaseSen; // $$ Case sensitive searching...
BOOL _fTextReg; // $$ regular expressions.
BOOL _fSearchSlowFiles; // && probably missleading as file over a 300baud modem is also slow
BOOL _fSearchSystemDirs; // Search system directories?
int _iNextConstraint; // which constraint to look at next...
HWND _hwnd; // for enum UI
SHCOLUMNID _scidDate; // which date property to operate on
SHCOLUMNID _scidSize; // which numeric property to operate on
IEnumIDList *_penumRoots; // idlist enumerator for search roots
IPropertyUI *_ppui; };
// Target folder queue.
class CFolderQueue { public: CFolderQueue() : _hdpa(NULL) {} ~CFolderQueue();
HRESULT Add(IShellFolder *psf, LPCITEMIDLIST pidl);
IShellFolder *Remove();
private: HRESULT _AddFolder(IShellFolder *psf); HDPA _hdpa; };
class CNamespaceEnum : public IFindEnum { public: CNamespaceEnum(IFindFilter *pfilter, IShellFolder *psf, IFindEnum *pdfEnumAsync, IEnumIDList *penumScopes, HWND hwnd, DWORD grfFlags, LPTSTR pszProgressText);
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release();
// IFindEnum
STDMETHODIMP Next(LPITEMIDLIST *ppidl, int *pcObjectSearched, int *pcFoldersSearched, BOOL *pfContinue, int *pState); STDMETHODIMP Skip(int celt) { return E_NOTIMPL; } STDMETHODIMP Reset() { return E_NOTIMPL; } STDMETHODIMP StopSearch(); STDMETHODIMP_(BOOL) FQueryIsAsync(); STDMETHODIMP GetAsyncCount(DBCOUNTITEM *pdwTotalAsync, int *pnPercentComplete, BOOL *pfQueryDone); STDMETHODIMP GetItemIDList(UINT iItem, LPITEMIDLIST *ppidl); STDMETHODIMP GetItemID(UINT iItem, DWORD *puWorkID); STDMETHODIMP SortOnColumn(UINT iCol, BOOL fAscending);
private: ~CNamespaceEnum(); BOOL _ShouldPushItem(LPCITEMIDLIST pidl); BOOL _IsSystemFolderByCLSID(LPCITEMIDLIST pidlFull); IShellFolder *_NextRootScope();
LONG _cRef; IFindFilter *_pfilter; // parent filter object
IFindFolder *_pff; // docfind folder interface over results
HWND _hwnd; // for enum UI
DWORD _grfFlags; // docfind enumeration flags (DFOO_xxx).
// Recursion state...
IShellFolder* _psf; // current shell folder
LPITEMIDLIST _pidlFolder; // current shell folder, as pidl
LPITEMIDLIST _pidlCurrentRootScope; // the last scope pulled out of _penumScopes
IEnumIDList *_penum; // current enumerator.
int _iFolder; // index of current folder in docfind results' folder list.
// filter info...
LPTSTR _pszProgressText; // path buffer pointer; caller-owned (evil!)
// enumeration state
IEnumIDList *_penumScopes; // Queue of target folders passed as arguments.
CFolderQueue _queSubFolders; // Queue of subfolders to search in next recursive pass.
// tree to store the exclude items (i.e. already seached)
CIDLTree _treeExcludeFolders;
// We may have an Async Enum that does some of the scopes...
IFindEnum *_penumAsync; };
// Constants used to keep track of how/why an item was added to the
// exclude tree.
enum { EXCLUDE_SEARCHED = 1, EXCLUDE_SYSTEMDIR = 2, };
// Create the default filter for our find code... They should be completly
// self contained...
STDAPI CreateNameSpaceFindFilter(IFindFilter **ppff) { CFindFilter *pff; HRESULT hr = E_OUTOFMEMORY;
pff = new CFindFilter(); if (pff) { hr = pff->InitSelf(); if (SUCCEEDED(hr)) { *ppff = pff; } else { pff->Release(); } }
return hr; }
HRESULT CFolderQueue::Add(IShellFolder *psf, LPCITEMIDLIST pidl) { IShellFolder *psfNew; HRESULT hr = SHBindToObject(psf, IID_X_PPV_ARG(IShellFolder, pidl, &psfNew)); if (SUCCEEDED(hr)) { hr = _AddFolder(psfNew); psfNew->Release(); } return hr; }
HRESULT CFolderQueue::_AddFolder(IShellFolder *psf) { HRESULT hr = E_OUTOFMEMORY; if (NULL == _hdpa) { _hdpa = DPA_Create(4); }
if (_hdpa) { if (DPA_AppendPtr(_hdpa, psf) >= 0) { psf->AddRef(); hr = S_OK; } } return hr; }
// remove the folder from the queue
// give the caller the ownership of this folder
IShellFolder *CFolderQueue::Remove() { IShellFolder *psf = NULL; if (_hdpa && DPA_GetPtrCount(_hdpa)) psf = (IShellFolder *)DPA_DeletePtr(_hdpa, 0); return psf; }
CFolderQueue::~CFolderQueue() { if (_hdpa) { while (TRUE) { IShellFolder *psf = Remove(); if (psf) { psf->Release(); } else { break; } } DPA_Destroy(_hdpa); } }
CFindFilter::CFindFilter() : _cRef(1), _wDateType(DFF_DATE_ALL), _ulQueryDialect(ISQLANG_V2) { }
CFindFilter::~CFindFilter() { Str_SetPtr(&_pszFileSpec, NULL); Str_SetPtr(&_pszSpecs, NULL); LocalFree(_apszFileSpecs); // elements point to pszSpecs so no free for them
Str_SetPtr(&_pszIndexedSearch, NULL);
if (_ppui) _ppui->Release();
if (_penumRoots) _penumRoots->Release(); }
STDMETHODIMP CFindFilter::InitSelf(void) { return _filtgrep.InitSelf(); }
STDMETHODIMP CFindFilter::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CFindFilter, IFindFilter), { 0 }, }; return QISearch(this, qit, riid, ppv); }
STDMETHODIMP_(ULONG) CFindFilter::AddRef() { return InterlockedIncrement(&_cRef); }
STDMETHODIMP_(ULONG) CFindFilter::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
// Retrieves the string resource index number that is proper for the
// current type of search.
STDMETHODIMP CFindFilter::GetStatusMessageIndex(UINT uContext, UINT *puMsgIndex) { // Currently context is not used
*puMsgIndex = IDS_FILESFOUND; return S_OK; }
// Retrieves which menu to load to merge for the folder
STDMETHODIMP CFindFilter::GetFolderMergeMenuIndex(UINT *puBGMainMergeMenu, UINT *puBGPopupMergeMenu) { *puBGMainMergeMenu = POPUP_DOCFIND_MERGE; *puBGPopupMergeMenu = 0; return S_OK; }
STDMETHODIMP CFindFilter::GetItemContextMenu(HWND hwndOwner, IFindFolder* pdfFolder, IContextMenu **ppcm) { return CFindItem_Create(hwndOwner, pdfFolder, ppcm); }
STDMETHODIMP CFindFilter::GetDefaultSearchGUID(IShellFolder2 *psf2, GUID *pGuid) { return DefaultSearchGUID(pGuid); }
STDMETHODIMP CFindFilter::EnumSearches(IShellFolder2 *psf2, IEnumExtraSearch **ppenum) { *ppenum = NULL; return E_NOTIMPL; }
STDMETHODIMP CFindFilter::GetSearchFolderClassId(GUID *pGuid) { *pGuid = CLSID_DocFindFolder; return S_OK; }
// (returns S_OK if nothing changed.)
STDMETHODIMP CFindFilter::FFilterChanged() { BOOL fFilterChanged = _fFilterChanged; this->_fFilterChanged = FALSE; return fFilterChanged ? S_FALSE : S_OK; }
// Generates the title given the current search criteria.
STDMETHODIMP CFindFilter::GenerateTitle(LPTSTR *ppszTitle, BOOL fFileName) { BOOL fFilePattern; int iRes; TCHAR szFindName[80]; // German should not exceed this find: ->???
LPTSTR pszFileSpec = _szUserInputFileSpec; LPTSTR pszText = _szText;
//
// Lets generate a title for the search. The title will depend on
// the file patern(s), the type field and the containing text field
// Complicate this a bit with the search for field...
//
fFilePattern = (pszFileSpec[0] != 0) && (lstrcmp(pszFileSpec, c_szStarDotStar) != 0);
if (!fFilePattern && (_penumAsync == NULL) && _pszIndexedSearch) { pszFileSpec = _pszIndexedSearch; fFilePattern = (pszFileSpec[0] != 0) && (lstrcmp(pszFileSpec, c_szStarDotStar) != 0); }
if ((pszText[0] == 0) && (_penumAsync != NULL) && _pszIndexedSearch) pszText = _pszIndexedSearch;
// First see if there is a type field
if (_iType > 0) { // We have a type field no check for content...
if (pszText[0] != 0) { // There is text!
// Should now use type but...
// else see if the name field is not NULL and not *.*
if (fFilePattern) iRes = IDS_FIND_TITLE_TYPE_NAME_TEXT; else iRes = IDS_FIND_TITLE_TYPE_TEXT; } else { // No type or text, see if file pattern
// Containing not found, first search for type then named
if (fFilePattern) iRes = IDS_FIND_TITLE_TYPE_NAME; else iRes = IDS_FIND_TITLE_TYPE; } } else { // No Type field ...
// first see if there is text to be searched for!
if (pszText[0] != 0) { // There is text!
// Should now use type but...
// else see if the name field is not NULL and not *.*
if (fFilePattern) iRes = IDS_FIND_TITLE_NAME_TEXT; else iRes = IDS_FIND_TITLE_TEXT; } else { // No type or text, see if file pattern
// Containing not found, first search for type then named
if (fFilePattern) iRes = IDS_FIND_TITLE_NAME; else iRes = IDS_FIND_TITLE_ALL; } }
// We put : in for first spot for title bar. For name creation
// we remove it which will put the number at the end...
if (!fFileName) LoadString(HINST_THISDLL, IDS_FIND_TITLE_FIND, szFindName, ARRAYSIZE(szFindName)); *ppszTitle = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(iRes), fFileName? szNULL : szFindName, _szTypeName, pszFileSpec, pszText);
return *ppszTitle ? S_OK : E_OUTOFMEMORY; }
STDMETHODIMP CFindFilter::ClearSearchCriteria() { // Also clear out a few other fields...
_szUserInputFileSpec[0] = 0; _iType = 0; _szText[0] = 0;
return S_OK; }
STDMETHODIMP CFindFilter::PrepareToEnumObjects(HWND hwnd, DWORD *pdwFlags) { *pdwFlags = 0; // start out empty
_hwnd = hwnd; // used for the first enum so that can do UI (auth/insert media)
// Update the flags and buffer strings
if (!_fTopLevelOnly) *pdwFlags |= DFOO_INCLUDESUBDIRS;
if (_fTextCaseSen) *pdwFlags |= DFOO_CASESEN; if (_fSearchSystemDirs) *pdwFlags |= DFOO_SEARCHSYSTEMDIRS;
// Also get the shell state variables to see if we should show extensions and the like
if (_fSearchHidden) *pdwFlags |= DFOO_SHOWALLOBJECTS;
// Now lets generate the file patern we will ask the system to look for
// Here is where we try to put some smarts into the file patterns stuff
// It will go something like:
// look between each; or , and see if there are any wild cards. If not
// do something like *patern*.
// Also if there is no search pattern or if it is * or *.*, set the
// filter to NULL as to speed it up.
//
_fANDSearch = SetupWildCardingOnFileSpec(_szUserInputFileSpec, &_pszFileSpec);
_cFileSpecs = 0; if (_pszFileSpec && _pszFileSpec[0]) { Str_SetPtr(&_pszSpecs, _pszFileSpec);
if (_pszSpecs) { int cTokens = 0; LPTSTR pszToken = _pszSpecs; // Count number of file spces
while (pszToken) { // let's walk pszFileSpec to see how many specs we have...
pszToken = StrChr(pszToken, TEXT(';'));
// If delimiter, then advance past for next iteration
if (pszToken) pszToken++; cTokens++; }
if (cTokens) { // cleanup the previous search
if (_apszFileSpecs) LocalFree(_apszFileSpecs); _apszFileSpecs = (LPTSTR *)LocalAlloc(LPTR, cTokens * sizeof(LPTSTR *)); if (_apszFileSpecs) { _cFileSpecs = cTokens; pszToken = _pszSpecs; for (int i = 0; i < cTokens; i++) { _apszFileSpecs[i] = pszToken; pszToken = StrChr(pszToken, TEXT(';')); if (pszToken) *pszToken++ = 0; } } } } }
_filtgrep.Reset(); HRESULT hr = S_OK; if (_szText[0]) { DWORD dwGrepFlags = FGIF_BLANKETGREP | FGIF_GREPFILENAME; if (*pdwFlags & DFOO_CASESEN) dwGrepFlags |= FGIF_CASESENSITIVE;
hr = _filtgrep.Initialize(GetACP(), _szText, NULL, dwGrepFlags); } return hr; }
STDMETHODIMP CFindFilter::GetColumnsFolder(IShellFolder2 **ppsf) { *ppsf = NULL; LPITEMIDLIST pidl; HRESULT hr = SHGetFolderLocation(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, 0, &pidl); if (SUCCEEDED(hr)) { hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder2, pidl, ppsf)); ILFree(pidl); } return hr; }
void FreePathArray(LPTSTR rgpszPaths[], UINT cPaths) { for (UINT i = 0; i < cPaths; i++) { LocalFree((HLOCAL)rgpszPaths[i]); rgpszPaths[i] = NULL; } }
HRESULT NamesFromEnumIDList(IEnumIDList *penum, LPTSTR rgpszPaths[], UINT sizePaths, UINT *pcPaths) { *pcPaths = 0; ZeroMemory(rgpszPaths, sizeof(rgpszPaths[0]) * sizePaths);
penum->Reset();
LPITEMIDLIST pidl; while (S_OK == penum->Next(1, &pidl, NULL)) { TCHAR szPath[MAX_PATH];
if (SUCCEEDED(SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), NULL))) { if ((*pcPaths) < sizePaths) { rgpszPaths[*pcPaths] = StrDup(szPath); if (rgpszPaths[*pcPaths]) (*pcPaths)++; } } ILFree(pidl); } return S_OK; }
void ClearIDArray(LPITEMIDLIST rgItems[], UINT cItems) { for (UINT i = 0; i < cItems; i++) { ILFree(rgItems[i]); rgItems[i] = NULL; } }
#define MAX_ROOTS 32
HRESULT FilterEnumeratorByNames(const LPCTSTR rgpszNames[], UINT cNames, IEnumIDList **ppenum) { LPITEMIDLIST rgItems[MAX_ROOTS] = {0}; int cItems = 0;
(*ppenum)->Reset(); // capture all of the other pidls in the current enumerator
LPITEMIDLIST pidl; while (S_OK == (*ppenum)->Next(1, &pidl, NULL)) { TCHAR szPath[MAX_PATH]; if (SUCCEEDED(SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), NULL))) { for (UINT i = 0; i < cNames; i++) { if (rgpszNames[i] && (cItems < ARRAYSIZE(rgItems)) && (0 == StrCmpIC(szPath, rgpszNames[i]))) { rgItems[cItems++] = pidl; pidl = NULL; // don't free below
break; } } } ILFree(pidl); // may be NULL
}
IEnumIDList *penum; if (SUCCEEDED(CreateIEnumIDListOnIDLists(rgItems, cItems, &penum))) { (*ppenum)->Release(); *ppenum = penum; }
ClearIDArray(rgItems, cItems);
return S_OK; }
HRESULT CFindFilter::_ScopeEnumerator(IEnumIDList **ppenum) { *ppenum = NULL; HRESULT hr = E_FAIL; if (_penumRoots) { hr = _penumRoots->Clone(ppenum); if (SUCCEEDED(hr)) (*ppenum)->Reset(); // clone above will clone the index as well
} return hr; }
//==========================================================================
//add helper funtion to check if the Path is restricted (WinseBUG 20189)
//==========================================================================
BOOL PathIsRestricted(TCHAR * szPath, RESTRICTIONS iFlag) { UINT driveNum, dwRest;
if((driveNum = PathGetDriveNumber(szPath)) != -1){ dwRest = SHRestricted(iFlag); if (dwRest & (1 << driveNum)) { return TRUE; } } return FALSE; }
void FilterNoViewDrives(LPTSTR rgpszNames[], UINT *pcNames) { UINT cNames = *pcNames; UINT cNamesResult = 0;
for (UINT iName = 0; iName < cNames; iName++) { if (PathIsRestricted(rgpszNames[iName], REST_NOVIEWONDRIVE)) { LocalFree((HLOCAL)rgpszNames[iName]); rgpszNames[iName] = NULL; } else { rgpszNames[cNamesResult++] = rgpszNames[iName]; } } *pcNames = cNamesResult; }
// produce the find enumerator
STDMETHODIMP CFindFilter::EnumObjects(IShellFolder *psf, LPCITEMIDLIST pidlStart, DWORD grfFlags, int iColSort, LPTSTR pszProgressText, IRowsetWatchNotify *prwn, IFindEnum **ppdfenum) { *ppdfenum = NULL;
HRESULT hr; IEnumIDList *penum; if (pidlStart) { hr = CreateIEnumIDListOnIDLists(&pidlStart, 1, &penum); } else { hr = _ScopeEnumerator(&penum); } if (SUCCEEDED(hr)) { UINT cPaths; LPTSTR rgpszPaths[MAX_ROOTS]; hr = NamesFromEnumIDList(penum, rgpszPaths, ARRAYSIZE(rgpszPaths), &cPaths); if (SUCCEEDED(hr)) { FilterNoViewDrives(rgpszPaths, &cPaths);
*ppdfenum = NULL;
if (cPaths > 0) { hr = CreateOleDBEnum(this, psf, rgpszPaths, &cPaths, grfFlags, iColSort, pszProgressText, prwn, ppdfenum); if (S_OK == hr && *ppdfenum != NULL) { _penumAsync = *ppdfenum; _penumAsync->AddRef(); } else { _penumAsync = NULL; }
// are their more paths to process?
if (cPaths) { // did user specified CI query that we can't grep for?
DWORD dwFlags; if (FAILED(GenerateQueryRestrictions(NULL, &dwFlags)) || !(dwFlags & GQR_REQUIRES_CI)) { FilterEnumeratorByNames(rgpszPaths, ARRAYSIZE(rgpszPaths), &penum);
IFindEnum *pdfenum = new CNamespaceEnum( SAFECAST(this, IFindFilter *), psf, *ppdfenum, penum, _hwnd, grfFlags, pszProgressText); if (pdfenum) { // The rest of the fields should be zero/NULL
*ppdfenum = pdfenum; hr = S_OK; } else { hr = E_OUTOFMEMORY; } } } } FreePathArray(rgpszPaths, cPaths); } penum->Release(); } return hr; }
// IFindFilter::MatchFilter
STDMETHODIMP_(BOOL) CFindFilter::MatchFilter(IShellFolder *psf, LPCITEMIDLIST pidl) { BOOL bMatch = TRUE; TCHAR szName[MAX_PATH], szDisplayName[MAX_PATH]; DWORD dwAttrib = SHGetAttributes(psf, pidl, SFGAO_HIDDEN | SFGAO_FOLDER | SFGAO_ISSLOW); if (SUCCEEDED(DisplayNameOf(psf, pidl, SHGDN_INFOLDER | SHGDN_FORPARSING, szName, ARRAYSIZE(szName))) && SUCCEEDED(DisplayNameOf(psf, pidl, SHGDN_INFOLDER | SHGDN_NORMAL, szDisplayName, ARRAYSIZE(szDisplayName)))) { IShellFolder2 *psf2; psf->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2)); // optional, may be NULL
// First things we dont show hidden files
// If show all is set then we should include hidden files also...
if (!_fSearchHidden && (SFGAO_HIDDEN & dwAttrib)) bMatch = FALSE; // does not match
if (bMatch && _fFoldersOnly && !(SFGAO_FOLDER & dwAttrib)) bMatch = FALSE; // does not match
if (bMatch && _iSizeType) { ULONGLONG ullSize; if (psf2 && SUCCEEDED(GetLongProperty(psf2, pidl, &_scidSize, &ullSize))) { if (1 == _iSizeType) // >
{ if (!(ullSize > _ullSize)) bMatch = FALSE; // does not match
} else if (2 == _iSizeType) // <
{ if (!(ullSize < _ullSize)) bMatch = FALSE; // does not match
} } else { bMatch = FALSE; } } if (bMatch && (_scidDate.fmtid != CLSID_NULL)) { FILETIME ft; if (psf2 && SUCCEEDED(GetDateProperty(psf2, pidl, &_scidDate, &ft))) { FILETIME ftLocal; FileTimeToLocalFileTime(&ft, &ftLocal);
WORD wFileDate = 0, wFileTime = 0; FileTimeToDosDateTime(&ftLocal, &wFileDate, &wFileTime); if (_dateModifiedBefore && !(wFileDate <= _dateModifiedBefore)) bMatch = FALSE; // does not match
if (_dateModifiedAfter && !(wFileDate >= _dateModifiedAfter)) bMatch = FALSE; // does not match
} else { bMatch = FALSE; } } // Match file specificaitions.
if (bMatch && _pszFileSpec && _pszFileSpec[0]) { // if we have split up version of the specs we'll use it because PathMatchSpec
// can take up to 5-6 hours for more than 10 wildcard specs
if (_cFileSpecs) { // Only search the actual file system file name if the user specified
// an extention
BOOL bHasExtension = (0 != *PathFindExtension(_pszFileSpec)); if (bHasExtension) { for (int i = 0; i < _cFileSpecs; i++) { bMatch = PathMatchSpec(szName, _apszFileSpecs[i]); if (_fANDSearch) { // AND we quit on the first one that doesn't match
if (!bMatch) break; } else { // OR we quit on the first one that does match
if (bMatch) break; } } } // Compare the displayable name to the filter.
// This is needed for searching the recylcle bin becuase the actual file names
// are similar to "DC0.LNK" instead of "New Text Document.txt"
if (!bMatch || !bHasExtension) { for (int i = 0; i < _cFileSpecs; i++) { bMatch = PathMatchSpec(szDisplayName, _apszFileSpecs[i]); if (_fANDSearch) { // AND we quit on the first one that doesn't match
if (!bMatch) break; } else { // OR we quit on the first one that does match
if (bMatch) break; } } } } else if (!PathMatchSpec(szName, _pszFileSpec) && !PathMatchSpec(szDisplayName, _pszFileSpec)) { bMatch = FALSE; // does not match
} } if (bMatch && _strTypeFilePatterns[0]) { // if looking for folders only and file pattern is all folders then no need to check
// if folder name matches the pattern -- we know it is the folder, otherwise we
// would have bailed out earlier in the function
if (!(_fFoldersOnly && lstrcmp(_strTypeFilePatterns, TEXT(".")) == 0)) { if (!PathMatchSpec(szName, _strTypeFilePatterns)) bMatch = FALSE; // does not match
} } // See if we need to do a grep of the file
if (bMatch && (SFGAO_ISSLOW & dwAttrib) && !_fSearchSlowFiles) bMatch = FALSE; // does not match
if (bMatch && (S_OK == _filtgrep.GetMatchTokens(NULL, 0) || S_OK == _filtgrep.GetExcludeTokens(NULL, 0))) { bMatch = (S_OK == _filtgrep.Grep(psf, pidl, szName)); } if (psf2) psf2->Release(); } else bMatch = FALSE; return bMatch; // return TRUE -> yes, a match!
}
// date ordinal mapper helpers to deal with old way to refer to dates
BOOL MapValueToDateSCID(UINT i, SHCOLUMNID *pscid) { ZeroMemory(pscid, sizeof(*pscid));
switch (i) { case 1: *pscid = SCID_WRITETIME; break; case 2: *pscid = SCID_CREATETIME; break; case 3: *pscid = SCID_ACCESSTIME; break;
default: return FALSE; } return TRUE; }
// returns 0 as invalid ordinal
int MapDateSCIDToValue(const SHCOLUMNID *pscid) { int i = 0; // 0 invalid scid
if (IsEqualSCID(*pscid, SCID_WRITETIME)) { i = 1; } else if (IsEqualSCID(*pscid, SCID_CREATETIME)) { i = 2; } else if (IsEqualSCID(*pscid, SCID_ACCESSTIME)) { i = 3; } return i; }
// IFindFilter::SaveCriteria
// util.cpp
STDAPI_(int) Int64ToString(LONGLONG n, LPTSTR szOutStr, UINT nSize, BOOL bFormat, NUMBERFMT *pFmt, DWORD dwNumFmtFlags); #define MAX_ULONGLONG_LEN 20+1 // "18446744073709551616"
STDMETHODIMP CFindFilter::SaveCriteria(IStream * pstm, WORD fCharType) { TCHAR szTemp[40]; // some random size
// The caller should have already validated the stuff and updated
// everything for the current filter information.
// we need to walk through and check each of the items to see if we
// have a criteria to save away. this includes:
// (Name, Path, Type, Contents, size, modification dates)
int cCriteria = _SaveCriteriaItem(pstm, IDD_FILESPEC, _szUserInputFileSpec, fCharType);
cCriteria += _SaveCriteriaItem(pstm, IDD_PATH, _szPath, fCharType);
cCriteria += _SaveCriteriaItem(pstm, DFSC_SEARCHFOR, _pszIndexedSearch, fCharType); cCriteria += _SaveCriteriaItem(pstm, IDD_TYPECOMBO, _strTypeFilePatterns, fCharType); cCriteria += _SaveCriteriaItem(pstm, IDD_CONTAINS, _szText, fCharType); // Also save away the state of the top level only
wsprintf(szTemp, TEXT("%d"), _fTopLevelOnly); cCriteria += _SaveCriteriaItem(pstm, IDD_TOPLEVELONLY, szTemp, fCharType);
// The Size field is little more fun!
if (_iSizeType != 0) { WCHAR szNum[MAX_ULONGLONG_LEN]; Int64ToString(_ullSize, szNum, ARRAYSIZE(szNum), FALSE, NULL, 0); wsprintf(szTemp, TEXT("%d %ws"), _iSizeType, szNum); cCriteria += _SaveCriteriaItem(pstm, IDD_SIZECOMP, szTemp, fCharType); }
// Likewise for the dates, should be fun as we need to save it depending on
// how the date was specified
switch (_wDateType & DFF_DATE_RANGEMASK) { case DFF_DATE_ALL: // nothing to store
break; case DFF_DATE_DAYS: wsprintf(szTemp, TEXT("%d"), _wDateValue); cCriteria += _SaveCriteriaItem(pstm, IDD_MDATE_NUMDAYS, szTemp, fCharType); break; case DFF_DATE_MONTHS: wsprintf(szTemp, TEXT("%d"), _wDateValue); cCriteria += _SaveCriteriaItem(pstm, IDD_MDATE_NUMMONTHS, szTemp, fCharType); break; case DFF_DATE_BETWEEN: if (_dateModifiedAfter) { wsprintf(szTemp, TEXT("%d"), _dateModifiedAfter); cCriteria += _SaveCriteriaItem(pstm, IDD_MDATE_FROM, szTemp, fCharType); }
if (_dateModifiedBefore) { wsprintf(szTemp, TEXT("%d"), _dateModifiedBefore); cCriteria += _SaveCriteriaItem(pstm, IDD_MDATE_TO, szTemp, fCharType); } break; }
if ((_wDateType & DFF_DATE_RANGEMASK) != DFF_DATE_ALL) { int i = MapDateSCIDToValue(&_scidDate); if (i) { // strangly we write a 0 based version of this ordinal out
wsprintf(szTemp, TEXT("%d"), i - 1); cCriteria += _SaveCriteriaItem(pstm, IDD_MDATE_TYPE, szTemp, fCharType); } }
if (_fTextCaseSen) { wsprintf(szTemp, TEXT("%d"), _fTextCaseSen); cCriteria += _SaveCriteriaItem(pstm, IDD_TEXTCASESEN, szTemp, fCharType); }
if (_fTextReg) { wsprintf(szTemp, TEXT("%d"), _fTextReg); cCriteria += _SaveCriteriaItem(pstm, IDD_TEXTREG, szTemp, fCharType); }
if (_fSearchSlowFiles) { wsprintf(szTemp, TEXT("%d"), _fSearchSlowFiles); cCriteria += _SaveCriteriaItem(pstm, IDD_SEARCHSLOWFILES, szTemp, fCharType); }
// Save value for searching system directories.
if (_fSearchSystemDirs) { wsprintf(szTemp, TEXT("%d"), _fSearchSystemDirs); cCriteria += _SaveCriteriaItem(pstm, IDD_SEARCHSYSTEMDIRS, szTemp, fCharType); }
if (_fSearchHidden) { wsprintf(szTemp, TEXT("%d"), _fSearchHidden); cCriteria += _SaveCriteriaItem(pstm, IDD_SEARCHHIDDEN, szTemp, fCharType); }
return MAKE_SCODE(0, 0, cCriteria); }
// Helper function for save criteria that will output the string and
// and id to the specified file. it will also test for NULL and the like
int CFindFilter::_SaveCriteriaItem(IStream *pstm, WORD wNum, LPCTSTR psz, WORD fCharType) { if ((psz == NULL) || (*psz == 0)) return 0; else { const void *pszText = (const void *)psz; // Ptr to output text. Defaults to source.
// These are required to support ANSI-unicode conversions.
LPSTR pszAnsi = NULL; // For unicode-to-ansi conversion.
LPWSTR pszWide = NULL; // For ansi-to-unicode conversion.
DFCRITERIA dfc; dfc.wNum = wNum; // Note: Problem if string is longer than 64K
dfc.cbText = (WORD) ((lstrlen(psz) + 1) * sizeof(TCHAR)); // Source string is Unicode but caller wants to save as ANSI.
//
if (DFC_FMT_ANSI == fCharType) { // Convert to ansi and write ansi.
dfc.cbText = (WORD) WideCharToMultiByte(CP_ACP, 0L, psz, -1, pszAnsi, 0, NULL, NULL); pszAnsi = (LPSTR)LocalAlloc(LMEM_FIXED, dfc.cbText); if (pszAnsi) { WideCharToMultiByte(CP_ACP, 0L, psz, -1, pszAnsi, dfc.cbText / sizeof(pszAnsi[0]), NULL, NULL); pszText = (void *)pszAnsi; } } pstm->Write(&dfc, sizeof(dfc), NULL); // Output index
pstm->Write(pszText, dfc.cbText, NULL); // output string + NULL
// Free up conversion buffers if any were created.
if (pszAnsi) LocalFree(pszAnsi); if (pszWide) LocalFree(pszWide); } return 1; }
// IFindFilter::RestoreCriteria
STDMETHODIMP CFindFilter::RestoreCriteria(IStream *pstm, int cCriteria, WORD fCharType) { SHSTR strTemp; SHSTRA strTempA;
if (cCriteria > 0) _fWeRestoredSomeCriteria = TRUE;
while (cCriteria--) { DFCRITERIA dfc; DWORD cb;
if (FAILED(pstm->Read(&dfc, sizeof(dfc), &cb)) || cb != sizeof(dfc)) break;
if (DFC_FMT_UNICODE == fCharType) { // Destination is Unicode and we're reading Unicode data from stream.
// No conversion required.
if (FAILED(strTemp.SetSize(dfc.cbText / sizeof(TCHAR))) || FAILED(pstm->Read(strTemp.GetInplaceStr(), dfc.cbText, &cb)) || (cb != dfc.cbText)) break; } else { // Destination is Unicode but we're reading ANSI data from stream.
// Read ansi. Convert to unicode.
if (FAILED(strTempA.SetSize(dfc.cbText / sizeof(CHAR))) || FAILED(pstm->Read(strTempA.GetInplaceStr(), dfc.cbText, &cb)) || (cb != dfc.cbText)) break;
strTemp.SetStr(strTempA); }
switch (dfc.wNum) { case IDD_FILESPEC: lstrcpyn(_szUserInputFileSpec, strTemp, ARRAYSIZE(_szUserInputFileSpec)); break;
case DFSC_SEARCHFOR: Str_SetPtr(&_pszIndexedSearch, strTemp); break;
case IDD_PATH: _ResetRoots(); lstrcpyn(_szPath, strTemp, ARRAYSIZE(_szPath)); CreateIEnumIDListPaths(_szPath, &_penumRoots); break;
case IDD_TOPLEVELONLY: _fTopLevelOnly = StrToInt(strTemp); break;
case IDD_TYPECOMBO: _strTypeFilePatterns.SetStr(strTemp); break;
case IDD_CONTAINS: lstrcpyn(_szText, strTemp, ARRAYSIZE(_szText)); break;
case IDD_SIZECOMP: // we need to extract off the two parts, the type and
// the value
_iSizeType = strTemp[0] - TEXT('0'); StrToInt64Ex(&(strTemp.GetStr())[2], STIF_DEFAULT, (LONGLONG*)&_ullSize); break;
case IDD_MDATE_NUMDAYS: _wDateType = DFF_DATE_DAYS; _wDateValue = (WORD) StrToInt(strTemp); break;
case IDD_MDATE_NUMMONTHS: _wDateType = DFF_DATE_MONTHS; _wDateValue = (WORD) StrToInt(strTemp); break;
case IDD_MDATE_FROM: _wDateType = DFF_DATE_BETWEEN; _dateModifiedAfter = (WORD) StrToInt(strTemp); break;
case IDD_MDATE_TO: _wDateType = DFF_DATE_BETWEEN; _dateModifiedBefore = (WORD) StrToInt(strTemp); break;
case IDD_MDATE_TYPE: // persisted value is zero based, adjust that by adding 1
MapValueToDateSCID(StrToInt(strTemp) + 1, &_scidDate); break;
case IDD_TEXTCASESEN: _fTextCaseSen = StrToInt(strTemp); break;
case IDD_TEXTREG: _fTextReg = StrToInt(strTemp); break;
case IDD_SEARCHSLOWFILES: _fSearchSlowFiles = StrToInt(strTemp); break; case IDD_SEARCHSYSTEMDIRS: _fSearchSystemDirs = StrToInt(strTemp); break; } } return S_OK; }
// IFindFilter::GetColSaveStream
STDMETHODIMP CFindFilter::GetColSaveStream(WPARAM wParam, IStream **ppstm) { *ppstm = OpenRegStream(HKEY_CURRENT_USER, REGSTR_PATH_EXPLORER, TEXT("DocFindColsX"), (DWORD) wParam); return *ppstm ? S_OK : E_FAIL; }
void CFindFilter::_GenerateQuery(LPWSTR pwszQuery, DWORD *pcchQuery) { DWORD cchNeeded = 0, cchLeft = *pcchQuery; LPWSTR pszCurrent = pwszQuery; BOOL bFirst = TRUE; // first property
if (_pszFileSpec && _pszFileSpec[0]) { cchNeeded += _CIQueryFilePatterns(&bFirst, &cchLeft, &pszCurrent, _pszFileSpec); }
// fFoldersOnly = TRUE implies szTypeFilePatterns = "."
// we cannot pass "." to CI because they won't understand it as give me the folder types
// we could check for @attrib ^a FILE_ATTRIBUTE_DIRECTORY (0x10) but ci doesn't index the
// folder names by default so we normally won't get any results...
if (!_fFoldersOnly && _strTypeFilePatterns[0]) { cchNeeded += _CIQueryFilePatterns(&bFirst, &cchLeft, &pszCurrent, _strTypeFilePatterns); } // Date:
if (_dateModifiedBefore) { cchNeeded += _QueryDosDate(&bFirst, &cchLeft, &pszCurrent, _dateModifiedBefore, TRUE); } if (_dateModifiedAfter) { cchNeeded += _QueryDosDate(&bFirst, &cchLeft, &pszCurrent, _dateModifiedAfter, FALSE); }
if (_iSizeType) { cchNeeded += _CIQuerySize(&bFirst, &cchLeft, &pszCurrent, _ullSize, _iSizeType); }
// Indexed Search: raw query
if (_pszIndexedSearch && _pszIndexedSearch[0]) { // HACK Alert if first Char is ! then we assume Raw and pass it through directly to CI...
// Likewise if it starts with @ or # pass through, but remember the @...
cchNeeded += _CIQueryIndex(&bFirst, &cchLeft, &pszCurrent, _pszIndexedSearch); }
// Containing Text:
if (_szText[0]) { // Try not to quote the strings unless we need to. This allows more flexability to do the
// searching for example: "cat near dog" is different than: cat near dog
cchNeeded += _CIQueryTextPatterns(&bFirst, &cchLeft, &pszCurrent, _szText, _fTextReg); }
cchNeeded += _CIQueryShellSettings(&bFirst, &cchLeft, &pszCurrent);
IEnumIDList *penum; if (SUCCEEDED(_ScopeEnumerator(&penum))) { TCHAR szPath[MAX_PATH];
LPITEMIDLIST pidl; while (S_OK == penum->Next(1, &pidl, NULL)) { if (SHGetPathFromIDList(pidl, szPath) && PathStripToRoot(szPath)) { // don't search recycle bin folder. we add both nt4's recycled
// and nt5's recycler for every drive we search.
static const LPCTSTR s_rgszRecycleBins[] = { TEXT("Recycled\\*"), TEXT("Recycler\\*"), };
for (int iBin = 0; iBin < ARRAYSIZE(s_rgszRecycleBins); iBin++) { TCHAR szExclude[MAX_PATH]; if (PathCombine(szExclude, szPath, s_rgszRecycleBins[iBin])) { DWORD cchSize = lstrlen(szExclude) + ARRAYSIZE(TEXT(" & !#PATH "));
// don't bail out early if we are asked for size of query
if (pwszQuery && cchSize > cchLeft) break;
cchNeeded += _AddToQuery(&pszCurrent, &cchLeft, TEXT(" & !#PATH ")); cchNeeded += _AddToQuery(&pszCurrent, &cchLeft, szExclude); } } } ILFree(pidl); } penum->Release(); }
// we must exclude the special folders from the results or ci will find items that
// we cannot get pidls for.
HKEY hkey; if (RegOpenKeyEx(HKEY_CURRENT_USER, CI_SPECIAL_FOLDERS, 0, KEY_READ, &hkey) == ERROR_SUCCESS) { DWORD cValues = 0; // init to zero in case query info bellow fails
RegQueryInfoKey(hkey, NULL, NULL, NULL, NULL, NULL, NULL, &cValues, NULL, NULL, NULL, NULL); for (DWORD i = 0; i < cValues; i++) { TCHAR szExcludePath[MAX_PATH]; DWORD cb = sizeof(szExcludePath);
TCHAR szName[10]; wsprintf(szName, TEXT("%d"), i); if (RegQueryValueEx(hkey, szName, NULL, NULL, (BYTE *)szExcludePath, &cb) == ERROR_SUCCESS) { // this is in the query (or a drive letter of the query)
DWORD cchSize = lstrlen(szExcludePath) + ARRAYSIZE(TEXT(" & !#PATH "));
// don't bail out early if we are asked for size of query
if (pwszQuery && cchSize > cchLeft) break;
cchNeeded += _AddToQuery(&pszCurrent, &cchLeft, TEXT(" & !#PATH ")); cchNeeded += _AddToQuery(&pszCurrent, &cchLeft, szExcludePath); } } RegCloseKey(hkey); }
// we need at least some constraints so give a query of "all files"
if (pwszQuery && pszCurrent == pwszQuery) _CIQueryFilePatterns(&bFirst, &cchLeft, &pszCurrent, L"*.*");
if (pszCurrent) { // Make sure we terminate the string at the end...
*pszCurrent = 0; }
if (!pwszQuery) { *pcchQuery = cchNeeded; } else { ASSERT(*pcchQuery > cchNeeded); } }
// Create a query command string out of the search criteria
STDMETHODIMP CFindFilter::GenerateQueryRestrictions(LPWSTR *ppwszQuery, DWORD *pdwQueryRestrictions) { // we should be able to make use of ci no matter what (exceptions at the end of the function)
DWORD dwQueryRestrictions = GQR_MAKES_USE_OF_CI; HRESULT hr = S_OK;
#ifdef DEBUG
if (GetKeyState(VK_SHIFT) < 0) { dwQueryRestrictions |= GQR_REQUIRES_CI; } #endif
if (ppwszQuery) { DWORD cchNeeded = 0; _GenerateQuery(NULL, &cchNeeded); cchNeeded++; // for \0
*ppwszQuery = (LPWSTR)LocalAlloc(LPTR, cchNeeded * sizeof(**ppwszQuery)); if (*ppwszQuery) { _GenerateQuery(*ppwszQuery, &cchNeeded); } else { hr = E_OUTOFMEMORY; } }
if (SUCCEEDED(hr)) { if (_pszIndexedSearch && _pszIndexedSearch[0]) dwQueryRestrictions |= GQR_REQUIRES_CI;
// ci is not case sensitive, so if user wanted case sensitive search we cannot use ci
// also ci doesn't index folder names by default so to be safe we just default to our
// disk traversal algorithm...
if (_fTextCaseSen || _fFoldersOnly) { if ((dwQueryRestrictions & GQR_REQUIRES_CI) && _fTextCaseSen) hr = MAKE_HRESULT(3, FACILITY_SEARCHCOMMAND, SCEE_CASESENINDEX); else if (dwQueryRestrictions & GQR_MAKES_USE_OF_CI) dwQueryRestrictions &= ~GQR_MAKES_USE_OF_CI; } } *pdwQueryRestrictions = dwQueryRestrictions; // return calculated Flags...
return hr; }
STDMETHODIMP CFindFilter::ReleaseQuery() { ATOMICRELEASE(_penumAsync); return S_OK; }
STDMETHODIMP CFindFilter::GetQueryLanguageDialect(ULONG* pulDialect) { *pulDialect = _ulQueryDialect; return S_OK; }
STDMETHODIMP CFindFilter::GetWarningFlags(DWORD* pdwWarningFlags) { *pdwWarningFlags = _dwWarningFlags; return S_OK; }
// Registering our interest in FS change notifications.
//
// In:
// hwnd = window handle of the find dialog
// uMsg = message to be sent to window when informing of notify
STDMETHODIMP CFindFilter::DeclareFSNotifyInterest(HWND hwnd, UINT uMsg) { HDPA hdpa = DPA_Create(10); // Used to manage list of pidls to add
if (hdpa) { IEnumIDList *penum; if (SUCCEEDED(_ScopeEnumerator(&penum))) { LPITEMIDLIST pidl; while (S_OK == penum->Next(1, &pidl, NULL)) { if (-1 == DPA_AppendPtr(hdpa, pidl)) { // Failed to add it, so free it.
ILFree(pidl); } } penum->Release(); } // Eliminate any pidls in the hdpa that are children of other pidls.
// this is needed to prevent receiving the multiple updates for one change.
// For example, if searching My Documents and C:\, then we will get 2 updates
// for a change in My Documents if My Documents is on the C:\.
int cItems = DPA_GetPtrCount(hdpa); for (int iOuterLoop = 0; iOuterLoop < cItems - 1; iOuterLoop++) { LPITEMIDLIST pidlOuter = (LPITEMIDLIST) DPA_GetPtr(hdpa, iOuterLoop); for (int iInnerLoop = iOuterLoop + 1; pidlOuter && iInnerLoop < cItems; iInnerLoop++) { LPITEMIDLIST pidlInner = (LPITEMIDLIST) DPA_GetPtr(hdpa, iInnerLoop); if (pidlInner) { if (ILIsParent(pidlInner, pidlOuter, FALSE)) { // Since pidlInner is pidlOuter's parent, free pidlOuter and
// don't register for events on it.
ILFree(pidlOuter); pidlOuter = NULL; DPA_SetPtr(hdpa, iOuterLoop, NULL); } else if (ILIsParent(pidlOuter, pidlInner, FALSE)) { // Since pidlOuter is pidlInner's parent, free pidlInner and
// don't register for events on it.
ILFree(pidlInner); pidlInner = NULL; DPA_SetPtr(hdpa, iInnerLoop, NULL); } } } } // Declare that we are interested in events on remaining pidls
for (int iRegIndex = 0; iRegIndex < cItems; iRegIndex++) { SHChangeNotifyEntry fsne = {0}; fsne.fRecursive = TRUE;
fsne.pidl = (LPITEMIDLIST)DPA_GetPtr(hdpa, iRegIndex); if (fsne.pidl) { SHChangeNotifyRegister(hwnd, SHCNRF_NewDelivery | SHCNRF_ShellLevel | SHCNRF_InterruptLevel, SHCNE_DISKEVENTS, uMsg, 1, &fsne); ILFree((LPITEMIDLIST)fsne.pidl); } }
DPA_Destroy(hdpa); } return S_OK; }
void CFindFilter::_UpdateTypeField(const VARIANT *pvar) { LPCWSTR pszValue = VariantToStrCast(pvar); // input needs to be a BSTR
if (pszValue) { if (StrStr(pszValue, TEXT(".Folder;."))) { // Special searching for folders...
_fFoldersOnly = TRUE; _strTypeFilePatterns.SetStr(TEXT(".")); } else { // Assume if the first one is wildcarded than all are,...
if (*pszValue == TEXT('*')) _strTypeFilePatterns.SetStr(pszValue); else { TCHAR szNextPattern[MAX_PATH]; // overkill in size
BOOL fFirst = TRUE; LPCTSTR pszNextPattern = pszValue; while ((pszNextPattern = NextPath(pszNextPattern, szNextPattern, ARRAYSIZE(szNextPattern))) != NULL) { if (!fFirst) _strTypeFilePatterns.Append(TEXT(";")); fFirst = FALSE;
if (szNextPattern[0] != TEXT('*')) _strTypeFilePatterns.Append(TEXT("*")); _strTypeFilePatterns.Append(szNextPattern); } } } } }
int _MapConstraint(LPCWSTR pszField) { for (int i = 0; i < ARRAYSIZE(s_cdffuf); i++) { if (StrCmpIW(pszField, s_cdffuf[i].pwszField) == 0) { return i; } } return -1; }
HRESULT CFindFilter::_GetPropertyUI(IPropertyUI **pppui) { if (!_ppui) SHCoCreateInstance(NULL, &CLSID_PropertiesUI, NULL, IID_PPV_ARG(IPropertyUI, &_ppui));
return _ppui ? _ppui->QueryInterface(IID_PPV_ARG(IPropertyUI, pppui)) : E_NOTIMPL; }
HRESULT CFindFilter::UpdateField(LPCWSTR pszField, VARIANT vValue) { _fFilterChanged = TRUE; // force rebuilding name of files...
USHORT uDosTime;
switch (_MapConstraint(pszField)) { case CDFFUFE_IndexedSearch: Str_SetPtr(&_pszIndexedSearch, NULL); // zero this out
_pszIndexedSearch = VariantToStr(&vValue, NULL, 0); break;
case CDFFUFE_LookIn: _ResetRoots();
if (FAILED(QueryInterfaceVariant(vValue, IID_PPV_ARG(IEnumIDList, &_penumRoots)))) { if (vValue.vt == VT_BSTR) { VariantToStr(&vValue, _szPath, ARRAYSIZE(_szPath)); CreateIEnumIDListPaths(_szPath, &_penumRoots); } else { LPITEMIDLIST pidl = VariantToIDList(&vValue); if (pidl) { CreateIEnumIDListOnIDLists(&pidl, 1, &_penumRoots); ILFree(pidl); } } } break;
case CDFFUFE_IncludeSubFolders: _fTopLevelOnly = !VariantToBOOL(vValue); // invert sense
break;
case CDFFUFE_Named: VariantToStr(&vValue, _szUserInputFileSpec, ARRAYSIZE(_szUserInputFileSpec)); break;
case CDFFUFE_ContainingText: ZeroMemory(_szText, sizeof(_szText)); // special zero init whole buffer
VariantToStr(&vValue, _szText, ARRAYSIZE(_szText)); break;
case CDFFUFE_FileType: _UpdateTypeField(&vValue); break;
case CDFFUFE_WhichDate: if (vValue.vt == VT_BSTR) { IPropertyUI *ppui; if (SUCCEEDED(_GetPropertyUI(&ppui))) { ULONG cch = 0; // in/out
ppui->ParsePropertyName(vValue.bstrVal, &_scidDate.fmtid, &_scidDate.pid, &cch); ppui->Release(); } } else { MapValueToDateSCID(VariantToInt(vValue), &_scidDate); } break;
case CDFFUFE_DateLE: _wDateType |= DFF_DATE_BETWEEN; VariantToDosDateTime(vValue, &_dateModifiedBefore, &uDosTime); if (_dateModifiedAfter && _dateModifiedBefore) { if (_dateModifiedAfter > _dateModifiedBefore) { WORD wTemp = _dateModifiedAfter; _dateModifiedAfter = _dateModifiedBefore; _dateModifiedBefore = wTemp; } } break;
case CDFFUFE_DateGE: _wDateType |= DFF_DATE_BETWEEN; VariantToDosDateTime(vValue, &_dateModifiedAfter, &uDosTime); if (_dateModifiedAfter && _dateModifiedBefore) { if (_dateModifiedAfter > _dateModifiedBefore) { WORD wTemp = _dateModifiedAfter; _dateModifiedAfter = _dateModifiedBefore; _dateModifiedBefore = wTemp; } } break;
case CDFFUFE_DateNDays: _wDateType |= DFF_DATE_DAYS; _wDateValue = (WORD)VariantToInt(vValue); _dateModifiedAfter = _GetTodaysDosDateMinusNDays(_wDateValue); break;
case CDFFUFE_DateNMonths: _wDateType |= DFF_DATE_MONTHS; _wDateValue = (WORD)VariantToInt(vValue); _dateModifiedAfter = _GetTodaysDosDateMinusNMonths(_wDateValue); break;
case CDFFUFE_SizeLE: _iSizeType = 2; _ullSize = VariantToULONGLONG(vValue); break;
case CDFFUFE_SizeGE: _iSizeType = 1; _ullSize = VariantToULONGLONG(vValue); break;
case CDFFUFE_TextCaseSen: _fTextCaseSen = VariantToBOOL(vValue); break;
case CDFFUFE_TextReg: _fTextReg = VariantToBOOL(vValue); break;
case CDFFUFE_SearchSlowFiles: _fSearchSlowFiles = VariantToBOOL(vValue); break;
case CDFFUFE_QueryDialect: _ulQueryDialect = VariantToUINT(vValue); break;
case CDFFUFE_WarningFlags: _dwWarningFlags = VariantToUINT(vValue); break;
case CDFFUFE_SearchSystemDirs: _fSearchSystemDirs = VariantToBOOL(vValue); break;
case CDFFUFE_SearchHidden: _fSearchHidden = VariantToBOOL(vValue); break; } return S_OK; }
void CFindFilter::_ResetRoots() { _szPath[0] = 0; ATOMICRELEASE(_penumRoots); }
HRESULT CFindFilter::ResetFieldsToDefaults() { // Try to reset everything that our UpdateFields may touch to make sure next search gets all
_ResetRoots();
_fTopLevelOnly = FALSE; _szUserInputFileSpec[0] = 0; _szText[0] = 0; if (_pszIndexedSearch) *_pszIndexedSearch = 0; _strTypeFilePatterns.Reset();
ZeroMemory(&_scidDate, sizeof(_scidDate)); _scidSize = SCID_SIZE;
_fFoldersOnly = FALSE; _wDateType = 0; _dateModifiedBefore = 0; _dateModifiedAfter = 0; _iSizeType = 0; _ullSize = 0; _fTextCaseSen = FALSE; _fTextReg = FALSE; _fSearchSlowFiles = FALSE; _ulQueryDialect = ISQLANG_V2; _dwWarningFlags = DFW_DEFAULT; _fSearchSystemDirs = FALSE;
// the search UI will usually override this, but if that us has not been updated
// we need to set out state the same was as before here
SHELLSTATE ss; SHGetSetSettings(&ss, SSF_SHOWALLOBJECTS, FALSE); _fSearchHidden = ss.fShowAllObjects; return S_OK; }
HRESULT CFindFilter::GetNextConstraint(VARIANT_BOOL fReset, BSTR *pName, VARIANT *pValue, VARIANT_BOOL *pfFound) { *pName = NULL; VariantClear(pValue); *pfFound = FALSE;
if (fReset) _iNextConstraint = 0;
HRESULT hr = S_FALSE; // not found
// we don't go to array size as the last entry is an empty item...
while (_iNextConstraint < ARRAYSIZE(s_cdffuf)) { switch (s_cdffuf[_iNextConstraint].cdffufe) { case CDFFUFE_IndexedSearch: hr = InitVariantFromStr(pValue, _pszIndexedSearch); break; case CDFFUFE_LookIn: hr = InitVariantFromStr(pValue, _szPath); break; case CDFFUFE_IncludeSubFolders: hr = InitVariantFromInt(pValue, _fTopLevelOnly ? 0 : 1); break; case CDFFUFE_Named: hr = InitVariantFromStr(pValue, _szUserInputFileSpec); break; case CDFFUFE_ContainingText: hr = InitVariantFromStr(pValue, _szText); break; case CDFFUFE_FileType: hr = InitVariantFromStr(pValue, _strTypeFilePatterns); break;
case CDFFUFE_WhichDate: pValue->lVal = MapDateSCIDToValue(&_scidDate); if (pValue->lVal) hr = InitVariantFromInt(pValue, pValue->lVal); break;
case CDFFUFE_DateLE: if ((_wDateType & DFF_DATE_RANGEMASK) == DFF_DATE_BETWEEN) hr = InitVariantFromDosDateTime(pValue, _dateModifiedBefore, 0); break;
case CDFFUFE_DateGE: if ((_wDateType & DFF_DATE_RANGEMASK) == DFF_DATE_BETWEEN) hr = InitVariantFromDosDateTime(pValue, _dateModifiedAfter, 0); break;
case CDFFUFE_DateNDays: if ((_wDateType & DFF_DATE_RANGEMASK) == DFF_DATE_DAYS) hr = InitVariantFromInt(pValue, _wDateValue); break;
case CDFFUFE_DateNMonths: if ((_wDateType & DFF_DATE_RANGEMASK) == DFF_DATE_MONTHS) hr = InitVariantFromInt(pValue, _wDateValue); break;
case CDFFUFE_SizeLE: if (_iSizeType == 2) hr = InitVariantFromULONGLONG(pValue, _ullSize); break;
case CDFFUFE_SizeGE: if (_iSizeType == 1) hr = InitVariantFromULONGLONG(pValue, _ullSize); break;
case CDFFUFE_TextCaseSen: hr = InitVariantFromInt(pValue, _fTextCaseSen ? 1 : 0); break;
case CDFFUFE_TextReg: hr = InitVariantFromInt(pValue, _fTextReg ? 1 : 0); break;
case CDFFUFE_SearchSlowFiles: hr = InitVariantFromInt(pValue, _fSearchSlowFiles ? 1 : 0); break;
case CDFFUFE_QueryDialect: hr = InitVariantFromUINT(pValue, _ulQueryDialect); break;
case CDFFUFE_WarningFlags: hr = InitVariantFromUINT(pValue, _dwWarningFlags); break;
case CDFFUFE_SearchSystemDirs: hr = InitVariantFromUINT(pValue, _fSearchSystemDirs ? 1 : 0); break;
case CDFFUFE_SearchHidden: hr = InitVariantFromUINT(pValue, _fSearchHidden ? 1 : 0); break; }
if (S_OK == hr) break;
if (SUCCEEDED(hr)) VariantClear(pValue);
_iNextConstraint += 1; }
if (S_OK == hr) { *pName = SysAllocString(s_cdffuf[_iNextConstraint].pwszField); if (NULL == *pName) { VariantClear(pValue); hr = E_OUTOFMEMORY; } else *pfFound = TRUE;
_iNextConstraint += 1; // for the next call here
} return hr; // no error let script use the found field...
}
DWORD CFindFilter::_AddToQuery(LPWSTR *ppszBuf, DWORD *pcchBuf, LPWSTR pszAdd) { DWORD cchAdd = lstrlenW(pszAdd);
if (*ppszBuf && *pcchBuf > cchAdd) { StrCpyNW(*ppszBuf, pszAdd, *pcchBuf); *pcchBuf -= cchAdd; *ppszBuf += cchAdd; } return cchAdd; }
DWORD AddQuerySep(DWORD *pcchBuf, LPWSTR *ppszCurrent, WCHAR bSep) { LPWSTR pszCurrent = *ppszCurrent; // make sure we have room for us plus terminator...
if (*ppszCurrent && *pcchBuf >= 4) { *pszCurrent++ = L' '; *pszCurrent++ = bSep; *pszCurrent++ = L' ';
*ppszCurrent = pszCurrent; *pcchBuf -= 3; } return 3; // size necessary
}
DWORD PrepareQueryParam(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent) { if (*pbFirst) { *pbFirst = FALSE; return 0; // no size necessary
} // we're not the first property
return AddQuerySep(pcchBuf, ppszCurrent, L'&'); }
// pick the longest date query so we can avoid checking the buffer size each time we
// add something to the string
#define LONGEST_DATE 50 //lstrlen(TEXT("{prop name=access} <= 2000/12/31 23:59:59{/prop}"))+2
DWORD CFindFilter::_QueryDosDate(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, WORD wDate, BOOL bBefore) { LPWSTR pszCurrent = *ppszCurrent; DWORD cchNeeded = PrepareQueryParam(pbFirst, pcchBuf, &pszCurrent); if (pszCurrent && *pcchBuf > LONGEST_DATE) { FILETIME ftLocal; DosDateTimeToFileTime(wDate, 0, &ftLocal); FILETIME ftGMT; LocalFileTimeToFileTime(&ftLocal, &ftGMT); SYSTEMTIME st; FileTimeToSystemTime(&ftGMT, &st);
IPropertyUI *ppui; if (SUCCEEDED(_GetPropertyUI(&ppui))) { WCHAR szName[128]; if (SUCCEEDED(ppui->GetCannonicalName(_scidDate.fmtid, _scidDate.pid, szName, ARRAYSIZE(szName)))) { wnsprintfW(pszCurrent, *pcchBuf, L"{prop name=%s} ", szName); // the date syntax we use is V2, so force this dialect
_ulQueryDialect = ISQLANG_V2; } ppui->Release(); }
pszCurrent += lstrlenW(pszCurrent); if (bBefore) { *pszCurrent++ = L'<'; // if you ask for a range like: 2/20/98 - 2/20/98 then we get no time at all
// So for before, convert H:m:ss to 23:59:59...
st.wHour = 23; st.wMinute = 59; st.wSecond = 59; } else { *pszCurrent++ = L'>'; } *pszCurrent++ = L'=';
wnsprintfW(pszCurrent, *pcchBuf, L" %d/%d/%d %d:%d:%d{/prop}", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); pszCurrent += lstrlenW(pszCurrent); *ppszCurrent = pszCurrent; *pcchBuf -= LONGEST_DATE; } return cchNeeded + LONGEST_DATE; }
DWORD CFindFilter::_CIQueryFilePatterns(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, LPCWSTR pszFilePatterns) { WCHAR szNextPattern[MAX_PATH]; // overkill in size
BOOL fFirst = TRUE; LPCWSTR pszNextPattern = pszFilePatterns; DWORD cchNeeded = PrepareQueryParam(pbFirst, pcchBuf, ppszCurrent);
// Currently will have to long hand the query, may try to find shorter format once bugs
// are fixed...
cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, L"("); while ((pszNextPattern = NextPathW(pszNextPattern, szNextPattern, ARRAYSIZE(szNextPattern))) != NULL) { if (!fFirst) { cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, L" | "); } fFirst = FALSE; cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, L"#filename "); cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, szNextPattern); } cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, L")"); return cchNeeded; }
DWORD CFindFilter::_CIQueryTextPatterns(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, LPWSTR pszText, BOOL bTextReg) { DWORD cchNeeded = PrepareQueryParam(pbFirst, pcchBuf, ppszCurrent);
cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, L"{prop name=all}"); cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, bTextReg? L"{regex}" : L"{phrase}"); cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, pszText); cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, bTextReg? L"{/regex}{/prop}" : L"{/phrase}{/prop}");
return cchNeeded; }
DWORD CFindFilter::_CIQuerySize(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, ULONGLONG ullSize, int iSizeType) { WCHAR szSize[MAX_ULONGLONG_LEN+8]; // +8 for " {/prop}"
DWORD cchNeeded = PrepareQueryParam(pbFirst, pcchBuf, ppszCurrent);
cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, L"{prop name=size} "); cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, iSizeType == 1? L">" : L"<"); WCHAR szNum[MAX_ULONGLONG_LEN]; Int64ToString(ullSize, szNum, ARRAYSIZE(szNum), FALSE, NULL, 0); wnsprintfW(szSize, *pcchBuf, L" %ws{/prop}", szNum); cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, szSize);
return cchNeeded; }
DWORD CFindFilter::_CIQueryIndex(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent, LPWSTR pszText) { DWORD cchNeeded = PrepareQueryParam(pbFirst, pcchBuf, ppszCurrent);
cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, pszText); return cchNeeded; }
DWORD CFindFilter::_CIQueryShellSettings(BOOL *pbFirst, DWORD *pcchBuf, LPWSTR *ppszCurrent) { DWORD cchNeeded = 0; if (!ShowSuperHidden()) { cchNeeded += PrepareQueryParam(pbFirst, pcchBuf, ppszCurrent); cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, L"NOT @attrib ^a 0x6 ");// don't show files w/ hidden and system bit on
}
SHELLSTATE ss; SHGetSetSettings(&ss, SSF_SHOWALLOBJECTS, FALSE); if (!ss.fShowAllObjects) { cchNeeded += PrepareQueryParam(pbFirst, pcchBuf, ppszCurrent); cchNeeded += _AddToQuery(ppszCurrent, pcchBuf, L"NOT @attrib ^a 0x2 "); // don't show files w/ hidden bit on
} return cchNeeded; }
// Helper function to add the PIDL from theCsidl to the exclude tree.
void _AddSystemDirCSIDLToPidlTree(int csidl, CIDLTree *ppidlTree) { LPITEMIDLIST pidl = SHCloneSpecialIDList(NULL, csidl, TRUE); if (pidl) { ppidlTree->AddData(IDLDATAF_MATCH_RECURSIVE, pidl, EXCLUDE_SYSTEMDIR); ILFree(pidl); } }
CNamespaceEnum::CNamespaceEnum(IFindFilter *pfilter, IShellFolder* psf, IFindEnum *penumAsync, IEnumIDList *penumScopes, HWND hwnd, DWORD grfFlags, LPTSTR pszProgressText) : _cRef(1), _pfilter(pfilter), _iFolder(-1), _hwnd(hwnd), _grfFlags(grfFlags), _pszProgressText(pszProgressText), _penumAsync(penumAsync) { ASSERT(NULL == _psf); ASSERT(NULL == _pidlFolder); ASSERT(NULL == _pidlCurrentRootScope); ASSERT(NULL == _penum);
if (penumScopes) penumScopes->Clone(&_penumScopes);
_pfilter->AddRef(); psf->QueryInterface(IID_PPV_ARG(IFindFolder, &_pff)); ASSERT(_pff);
if (_penumAsync) _penumAsync->AddRef();
// Setup the exclude system directories:
if (!(_grfFlags & DFOO_SEARCHSYSTEMDIRS)) { // IE History and cache are excluded based on the CLSID.
_AddSystemDirCSIDLToPidlTree(CSIDL_WINDOWS, &_treeExcludeFolders); _AddSystemDirCSIDLToPidlTree(CSIDL_PROGRAM_FILES, &_treeExcludeFolders);
// Exclude Temp folder
TCHAR szPath[MAX_PATH]; if (GetTempPath(ARRAYSIZE(szPath), szPath)) { LPITEMIDLIST pidl = ILCreateFromPath(szPath); if (pidl) { _treeExcludeFolders.AddData(IDLDATAF_MATCH_RECURSIVE, pidl, EXCLUDE_SYSTEMDIR); ILFree(pidl); } } } }
CNamespaceEnum::~CNamespaceEnum() { ATOMICRELEASE(_penumScopes);
ATOMICRELEASE(_pfilter); ATOMICRELEASE(_psf); ATOMICRELEASE(_penumAsync); ATOMICRELEASE(_pff);
ILFree(_pidlFolder); // accepts NULL
ILFree(_pidlCurrentRootScope); }
STDMETHODIMP CNamespaceEnum::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { // QITABENT(CNamespaceEnum, IFindEnum),
{ 0 }, }; return QISearch(this, qit, riid, ppv); }
STDMETHODIMP_(ULONG) CNamespaceEnum::AddRef() { return InterlockedIncrement(&_cRef); }
STDMETHODIMP_(ULONG) CNamespaceEnum::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
// Check if the relative pidl passed is to a folder that we are going
// skip based on its CLSID:
// This will be used to skip IE History and IE Cache.
BOOL CNamespaceEnum::_IsSystemFolderByCLSID(LPCITEMIDLIST pidl) { BOOL bRetVal = FALSE; IShellFolder2 *psf2; if (_psf && SUCCEEDED(_psf->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2)))) { CLSID clsid; if (SUCCEEDED(GetItemCLSID(psf2, pidl, &clsid))) { if (IsEqualCLSID(clsid, CLSID_CacheFolder) || IsEqualCLSID(clsid, CLSID_CacheFolder2) || IsEqualCLSID(clsid, CLSID_HistFolder)) { bRetVal = TRUE; } } psf2->Release(); } return bRetVal; }
// given the fact that a file is a directory, is this one we should search???
BOOL CNamespaceEnum::_ShouldPushItem(LPCITEMIDLIST pidl) { BOOL bShouldPush = FALSE; TCHAR szName[MAX_PATH];
// folders only, not folder shortcuts (note, this includes SFGAO_STREAM objects, .zip/.cab files)
// skip all folders that start with '?'. these are folders that the names got trashed in some
// ansi/unicode round trip. this avoids problems in the web folders name space
if (SFGAO_FOLDER == SHGetAttributes(_psf, pidl, SFGAO_FOLDER | SFGAO_LINK) && SUCCEEDED(DisplayNameOf(_psf, pidl, SHGDN_INFOLDER | SHGDN_FORPARSING, szName, ARRAYSIZE(szName))) && (TEXT('?') != szName[0])) { LPITEMIDLIST pidlFull = ILCombine(_pidlFolder, pidl); if (pidlFull) { INT_PTR i = 0; // Check if the folder is in the exclude list because it has been searched
HRESULT hr = _treeExcludeFolders.MatchOne(IDLDATAF_MATCH_RECURSIVE, pidlFull, &i, NULL);
if (FAILED(hr)) { // see if an alias version of this pidl is exists
LPITEMIDLIST pidlAlias = SHLogILFromFSIL(pidlFull); if (pidlAlias) { hr = _treeExcludeFolders.MatchOne(IDLDATAF_MATCH_RECURSIVE, pidlAlias, &i, NULL); ILFree(pidlAlias); } }
if (FAILED(hr)) { // If we still think it should be added, check if we can reject it based
// on its CSILD. We will only exclude system folders this way.
bShouldPush = (_grfFlags & DFOO_SEARCHSYSTEMDIRS) || (!_IsSystemFolderByCLSID(pidl)); } else if (i == EXCLUDE_SYSTEMDIR) { // If it is under a system directory exclude, check to see if it is the
// directory or a sub directory. We want to exclude the exact directory
// so that we don't add a system directory to the list of things to search.
// Since we may have specified the directory in the list of places to search
// and therefore want to search it, we don't want to exclude sub directories
// that way.
hr = _treeExcludeFolders.MatchOne(IDLDATAF_MATCH_EXACT, pidlFull, &i, NULL); if (FAILED(hr)) { // If we get here, it means that pidlFull is a sub directory of a
// system directory which was searched because it was explicitly
// specified in the list of scopes to search. Therefore we want
// to continue to search the sub directories.
bShouldPush = TRUE; } } else { // It matched an item in the tree and was not EXCLUDE_SYSTEMDIR:
ASSERT(i == EXCLUDE_SEARCHED); } ILFree(pidlFull); } }
return bShouldPush; }
IShellFolder *CNamespaceEnum::_NextRootScope() { IShellFolder *psf = NULL;
if (_penumScopes) { LPITEMIDLIST pidl; if (S_OK == _penumScopes->Next(1, &pidl, NULL)) { SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidl, &psf)); ILFree(pidl); } } return psf; }
STDMETHODIMP CNamespaceEnum::Next(LPITEMIDLIST *ppidl, int *pcObjectSearched, int *pcFoldersSearched, BOOL *pfContinue, int *piState) { *ppidl = NULL; *piState = GNF_NOMATCH; HRESULT hrRet = S_FALSE; while (S_OK != hrRet && *pfContinue) { // Retrieve an enumerator if we don't already have one.
while (NULL == _penum) { // Indicates that we have taken scope from _penumScopes
BOOL fUseRootScope = FALSE;
ASSERT(NULL == _psf);
// first try popping subdirs off folder stack
_psf = _queSubFolders.Remove();
// If there are no folders pushed, then try to get on from the caller.. (root scopes)
if (NULL == _psf) { // Since we are getting a new root scope, add old one to exclude list
if (_pidlCurrentRootScope) { // Add to exclude List.
if (_grfFlags & DFOO_INCLUDESUBDIRS) { // Since all sub directories will be search, don't search again.
_treeExcludeFolders.AddData(IDLDATAF_MATCH_RECURSIVE, _pidlCurrentRootScope, EXCLUDE_SEARCHED); } else { // Since sub directories have not been search, allow them to be searched.
_treeExcludeFolders.AddData(IDLDATAF_MATCH_EXACT, _pidlCurrentRootScope, EXCLUDE_SEARCHED); }
ILFree(_pidlCurrentRootScope); _pidlCurrentRootScope = NULL; } // Get scope from list passed in from caller (root scopes)
_psf = _NextRootScope();
fUseRootScope = TRUE; } if (_psf) { HRESULT hrT = SHGetIDListFromUnk(_psf, &_pidlFolder);
if (SUCCEEDED(hrT) && fUseRootScope) { // Check if the pidl is in the tree
INT_PTR i = 0; HRESULT hrMatch = _treeExcludeFolders.MatchOne(IDLDATAF_MATCH_RECURSIVE, _pidlFolder, &i, NULL); // If we have a new root scope, set that up as current root scope pidl:
// We only want to skip pidls from the queue of "root" search scopes
// if they have already been searched. We do not want to exclude a directory
// (from exclude system directories) if it is an explicit search scope.
if (FAILED(hrMatch) || i == EXCLUDE_SYSTEMDIR) { ASSERT(_pidlCurrentRootScope == NULL); _pidlCurrentRootScope = ILClone(_pidlFolder); } else { // Since the PIDL is in the exclude tree, we do not want to search it.
hrT = E_FAIL; } }
if (SUCCEEDED(hrT)) { // Check that we have a pidl, that it is not to be excluded, and that it can
// be enumerated.
SHCONTF contf = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS; if (_grfFlags & DFOO_SHOWALLOBJECTS) contf |= SHCONTF_INCLUDEHIDDEN; hrT = _psf->EnumObjects(_hwnd, contf, &_penum);
// only do UI on the first enum, all others are silent
// this makes doing search on A:\ produce the insert media
// prompt, but searching MyComputer does not prompt for all
// empty media
_hwnd = NULL;
if (S_OK == hrT) { SHGetNameAndFlags(_pidlFolder, SHGDN_FORPARSING | SHGDN_FORADDRESSBAR, _pszProgressText, MAX_PATH, NULL); (*pcFoldersSearched)++; } }
// Check for cleaning up...
if (hrT != S_OK) { ASSERT(NULL == _penum); ATOMICRELEASE(_psf); // and continue...
ILFree(_pidlFolder); _pidlFolder = NULL; } } else // no scope
{ *piState = GNF_DONE; return hrRet; } }
// Retrieve item
LPITEMIDLIST pidl; HRESULT hrEnum = S_FALSE;
while ((S_OK != hrRet) && *pfContinue && (S_OK == (hrEnum = _penum->Next(1, &pidl, NULL)))) { (*pcObjectSearched)++;
// Determine if this is a subfolder that should be recursively searched.
if (_grfFlags & DFOO_INCLUDESUBDIRS) { if (_ShouldPushItem(pidl)) { // queue folder to stack of subfolders to be searched in the next round.
_queSubFolders.Add(_psf, pidl); } }
// Test item against search criteria:
if (_pfilter->MatchFilter(_psf, pidl)) { // folder has not been registered with docfind folder?
if (_iFolder < 0) { // add folder to folder list.
_pff->AddFolder(_pidlFolder, TRUE, &_iFolder); ASSERT(_iFolder >= 0); } // add docfind goo to pidl
hrRet = _pff->AddDataToIDList(pidl, _iFolder, _pidlFolder, DFDF_NONE, 0, 0, 0, ppidl); if (SUCCEEDED(hrRet)) *piState = GNF_MATCH; } else { ASSERT(GNF_NOMATCH == *piState); hrRet = S_OK; // exit loop, without a match
} ILFree(pidl); }
if (!*pfContinue) { *piState = GNF_DONE; hrEnum = S_FALSE; } if (S_OK != hrEnum) { // Done enumerating this folder - clean up for next iteration; or..
// Failed miserably - clean up prior to bail.
ATOMICRELEASE(_penum); ATOMICRELEASE(_psf); ILFree(_pidlFolder); _pidlFolder = NULL; _iFolder = -1; } } return hrRet; }
STDMETHODIMP CNamespaceEnum::StopSearch() { if (_penumAsync) return _penumAsync->StopSearch(); return E_NOTIMPL; }
STDMETHODIMP_(BOOL) CNamespaceEnum::FQueryIsAsync() { if (_penumAsync) return DF_QUERYISMIXED; // non-zero special number to say both...
return FALSE; }
STDMETHODIMP CNamespaceEnum::GetAsyncCount(DBCOUNTITEM *pdwTotalAsync, int *pnPercentComplete, BOOL *pfQueryDone) { if (_penumAsync) return _penumAsync->GetAsyncCount(pdwTotalAsync, pnPercentComplete, pfQueryDone);
*pdwTotalAsync = 0; return E_NOTIMPL; }
STDMETHODIMP CNamespaceEnum::GetItemIDList(UINT iItem, LPITEMIDLIST *ppidl) { if (_penumAsync) return _penumAsync->GetItemIDList(iItem, ppidl);
*ppidl = NULL; return E_NOTIMPL; }
STDMETHODIMP CNamespaceEnum::GetItemID(UINT iItem, DWORD *puWorkID) { if (_penumAsync) return _penumAsync->GetItemID(iItem, puWorkID);
*puWorkID = (UINT)-1; return E_NOTIMPL; }
STDMETHODIMP CNamespaceEnum::SortOnColumn(UINT iCol, BOOL fAscending) { if (_penumAsync) return _penumAsync->SortOnColumn(iCol, fAscending);
return E_NOTIMPL; }
// Masks used to indicate which search operation we are doing.
#define AND_MASK 0x01
#define OR_MASK 0x02
#define SEMICOLON_MASK 0x04
#define COMMA_MASK 0x08
#define EXTENSION_MASK 0x10
// Both "*" and "?" are treated as wildcards.
BOOL SetupWildCardingOnFileSpec(LPTSTR pszSpecIn, LPTSTR *ppszSpecOut) { LPTSTR pszIn = pszSpecIn; BOOL fQuote; TCHAR szSpecOut[3*MAX_PATH]; // Rather large...
// Read in localized versions of AND/OR used for searching
TCHAR szAND[20]; LoadString(HINST_THISDLL, IDS_FIND_AND, szAND, ARRAYSIZE(szAND)); TCHAR szOR[20]; LoadString(HINST_THISDLL, IDS_FIND_OR, szOR, ARRAYSIZE(szOR));
// Masks and variable to indicate what operation we are going to perform.
UINT iOperation = 0; // Bitmask to store which operation we have selected.
// allocate a buffer that should be able to hold the resultant
// string. When all is said and done we'll re-allocate to the
// correct size.
LPTSTR pszOut = szSpecOut; while (*pszIn != 0) { TCHAR c; // The delimiter.
LPTSTR pszT; int ich;
// Strip in leading spaces out of there
while (*pszIn == TEXT(' ')) pszIn++;
if (*pszIn == 0) break;
// If we are beyond the first item, add a seperator between the tokens
if (pszOut != szSpecOut) *pszOut++ = TEXT(';'); fQuote = (*pszIn == TEXT('"')); if (fQuote) { // The user asked for something litteral.
pszT = pszIn = CharNext(pszIn); while (*pszT && (*pszT != TEXT('"'))) pszT = CharNext(pszT); } else { pszT = pszIn + (ich = StrCSpn(pszIn, TEXT(",; \""))); // Find end of token
}
c = *pszT; // Save away the seperator character that was found
*pszT = 0; // Add null so string functions will work and only extract the token
// Put in a couple of tests for * and *.*
if ((lstrcmp(pszIn, c_szStar) == 0) || (lstrcmp(pszIn, c_szStarDotStar) == 0)) { // Complete wild card so set a null criteria
*pszT = c; // Restore char;
pszOut = szSpecOut; // Set to start of string
break; } if (fQuote) { lstrcpy(pszOut, pszIn); pszOut += lstrlen(pszIn); } else if (lstrcmpi(pszIn, szAND) == 0) { iOperation |= AND_MASK; // If we don't move back one character, then "New and folder" will give:
// "*New*;;*folder*"
if (pszOut != szSpecOut) --pszOut; } else if (lstrcmpi(pszIn, szOR) == 0) { iOperation |= OR_MASK; // If we don't move back one character, then "New or folder" will give:
// "*New*;;*folder*"
if (pszOut != szSpecOut) --pszOut; } else if (*pszIn == 0) { // If we don't move back one character, then "New ; folder" will give:
// "*New*;**;*folder*"
if (pszOut != szSpecOut) --pszOut;
// Check what the seperator is. This handles instances like
// ("abba" ; "abba2") where we want an OR search.
if (c == TEXT(',')) { iOperation |= COMMA_MASK; } else if (c == TEXT(';')) { iOperation |= SEMICOLON_MASK; } } else { // Check what the seperator is:
if (c == TEXT(',')) { iOperation |= COMMA_MASK; } else if (c == TEXT(';')) { iOperation |= SEMICOLON_MASK; } // both "*" and "?" are wildcards. When checking for wildcards check
// for both before we conclude there are no wildcards. If a search
// string contains both "*" and "?" then we need for pszStar to point
// to the last occorance of either one (this is assumed in the code
// below which will add a ".*" when pszStar is the last character).
// NOTE: I wish there was a StrRPBrk function to do this for me.
LPTSTR pszStar = StrRChr(pszIn, NULL, TEXT('*')); LPTSTR pszAnyC = StrRChr(pszIn, NULL, TEXT('?')); if (pszAnyC > pszStar) pszStar = pszAnyC; if (pszStar == NULL) { // No wildcards were used:
*pszOut++ = TEXT('*'); lstrcpy(pszOut, pszIn); pszOut += ich; *pszOut++ = TEXT('*'); } else { // Includes wild cards
lstrcpy(pszOut, pszIn); pszOut += ich;
pszAnyC = StrRChr(pszIn, NULL, TEXT('.')); if (pszAnyC) { // extension present, that implies OR search
iOperation |= EXTENSION_MASK; } else { // No extension is given
if ((*(pszStar+1) == 0) && (*pszStar == TEXT('*'))) { // The last character is an "*" so this single string will
// match everything you would expect.
} else { // Before, given "a*a" we searched for "a*a" as well
// as "a*a.*". We can no longer do that because if we are
// doing an AND search, it will exclude any item that does not
// match both of the criterial. For example, "abba" mattches
// "a*a" but not "a*a.*" and "abba.txt" matches "a*a.*" but
// not "a*a". Therefore, we append an * to get "a*a*". This
// will match files like "abba2.wav" which wouldn't previously
// have been matched, but it is a small price to pay.
*pszOut++ = TEXT('*'); } } } }
*pszT = c; // Restore char;
if (c == 0) break;
// Skip the seperator except if we weren't quoting and the seperator is
// a '"' then we found something like (blah"next tag")
if (*pszT != 0 && !(*pszT == TEXT('"') && !fQuote)) pszT++; pszIn = pszT; // setup for the next item
} // Ensure the string is terminated
*pszOut++ = 0;
// re-alloc the buffer down to the actual size of the string...
Str_SetPtr(ppszSpecOut, szSpecOut); // Precidence rules to be applied in order:
// 1. ; -> OR search
// 2. AND -> AND search
// 3. , or OR -> OR search
// 4. none & explict file extensions -> OR search (files can only have one extension)
// 5. none -> AND search
//
//
// AND OR ; , | AND Search
// X X 1 X | 0
// 1 X 0 X | 1
// 0 \ 0 \ | 0 Where one if the '\'s is 1
// 0 0 0 0 | 1
return (!(iOperation & SEMICOLON_MASK) && (iOperation & AND_MASK)) || iOperation == 0; }
WORD CFindFilter::_GetTodaysDosDateMinusNDays(int nDays) { SYSTEMTIME st; union { FILETIME ft; LARGE_INTEGER li; }ftli;
WORD FatTime = 0, FatDate = 0;
// Now we need to
GetSystemTime(&st); SystemTimeToFileTime(&st, &ftli.ft); FileTimeToLocalFileTime(&ftli.ft, &ftli.ft);
// Now decrement the file time by the count of days * the number of
// 100NS time units per day. Assume that nDays is positive.
if (nDays > 0) { #define NANO_SECONDS_PER_DAY 864000000000
ftli.li.QuadPart = ftli.li.QuadPart - ((__int64)nDays * NANO_SECONDS_PER_DAY); }
FileTimeToDosDateTime(&ftli.ft, &FatDate, &FatTime); DebugMsg(DM_TRACE, TEXT("DocFind %d days = %x"), nDays, FatDate); return FatDate; }
WORD CFindFilter::_GetTodaysDosDateMinusNMonths(int nMonths) { SYSTEMTIME st; FILETIME ft; WORD FatTime, FatDate;
GetSystemTime(&st); st.wYear -= (WORD) nMonths / 12; nMonths = nMonths % 12; if (nMonths < st.wMonth) st.wMonth -= (WORD) nMonths; else { st.wYear--; st.wMonth = (WORD)(12 - (nMonths - st.wMonth)); }
// Now normalize back to a valid date.
while (!SystemTimeToFileTime(&st, &ft)) { st.wDay--; // must not be valid date for month...
}
if (!FileTimeToLocalFileTime(&ft, &ft) || !FileTimeToDosDateTime(&ft, &FatDate,&FatTime)) FatDate = 0; //search for all the files from beginning of time (better to find more than less)
DebugMsg(DM_TRACE, TEXT("DocFind %d months = %x"), nMonths, FatDate); return FatDate; }
|