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.
2962 lines
98 KiB
2962 lines
98 KiB
#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;
|
|
}
|