1 /////////////////////////////////////////////////////////////////////////////
2 // FileFilterMgr.cpp : implementation file
3 // see FileFilterMgr.h for description
4 /////////////////////////////////////////////////////////////////////////////
6 // This program is free software; you can redistribute it and/or modify it
7 // under the terms of the GNU General Public License as published by the
8 // Free Software Foundation; either version 2 of the License, or (at your
9 // option) any later version.
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 /////////////////////////////////////////////////////////////////////////////
19 * @file FileFilterMgr.cpp
21 * @brief Implementation of FileFilterMgr and supporting routines
23 // ID line follows -- this is updated by SVN
29 #include "UnicodeString.h"
31 #include "FileFilterMgr.h"
33 #include "coretools.h"
39 static char THIS_FILE[] = __FILE__;
45 * @brief Deletes items from filter list.
47 * @param [in] filterList List to empty.
49 void EmptyFilterList(vector<FileFilterElement*> *filterList)
51 while (!filterList->empty())
53 FileFilterElement *elem = filterList->back();
54 pcre_free(elem->pRegExp);
55 pcre_free(elem->pRegExpExtra);
57 filterList->pop_back();
62 * @brief One actual filter.
64 * For example, this might be a GNU C filter, excluding *.o files and CVS
65 * directories. That is to say, a filter is a set of file masks and
66 * directory masks. Usually FileFilter contains rules from one filter
67 * definition file. So it can be thought as filter file contents.
72 bool default_include; /**< If true, filter rules are inclusive by default */
73 CString name; /**< Filter name (shown in UI) */
74 CString description; /**< Filter description text */
75 CString fullpath; /**< Full path to filter file */
76 vector<FileFilterElement*> filefilters; /**< List of rules for files */
77 vector<FileFilterElement*> dirfilters; /**< List of rules for directories */
78 FileFilter() : default_include(true) { }
83 * @brief Destructor, frees created filter lists.
85 FileFilter::~FileFilter()
87 EmptyFilterList(&filefilters);
88 EmptyFilterList(&dirfilters);
92 * @brief Destructor, frees all filters.
94 FileFilterMgr::~FileFilterMgr()
100 * @brief Loads filterfile from disk and adds it to filters.
101 * @param [in] szFilterFile Filter file to load.
102 * @return FILTER_OK if succeeded or one of FILTER_RETVALUE values on error.
104 int FileFilterMgr::AddFilter(LPCTSTR szFilterFile)
106 int errorcode = FILTER_OK;
107 FileFilter * pFilter = LoadFilterFile(szFilterFile, errorcode);
109 m_filters.push_back(pFilter);
114 * @brief Load all filter files matching pattern from disk into internal filter set.
116 * @param [in] szPattern Pattern from where to load filters, for example "\\Filters\\*.flt"
117 * @param [in] szExt File-extension of filter files
119 void FileFilterMgr::LoadFromDirectory(LPCTSTR szPattern, LPCTSTR szExt)
122 BOOL bWorking = finder.FindFile(szPattern);
123 int extlen = szExt ? _tcslen(szExt) : 0;
126 bWorking = finder.FindNextFile();
127 if (finder.IsDots() || finder.IsDirectory())
129 CString sFilename = finder.GetFileName();
132 // caller specified a specific extension
133 // (This is really a workaround for brokenness in windows, which
134 // doesn't screen correctly on extension in pattern)
135 if (sFilename.Right(extlen).CompareNoCase(szExt))
138 AddFilter(finder.GetFilePath());
143 * @brief Removes filter from filterlist.
145 * @param [in] szFilterFile Filename of filter to remove.
147 void FileFilterMgr::RemoveFilter(LPCTSTR szFilterFile)
149 // Note that m_filters.GetSize can change during loop
150 vector<FileFilter*>::iterator iter = m_filters.begin();
151 while (iter != m_filters.end())
153 if ((*iter)->fullpath.CompareNoCase(szFilterFile) == 0)
156 m_filters.erase(iter);
164 * @brief Removes all filters from current list.
166 void FileFilterMgr::DeleteAllFilters()
168 while (!m_filters.empty())
170 FileFilter* filter = m_filters.back();
172 m_filters.pop_back();
177 * @brief Add a single pattern (if nonempty & valid) to a pattern list.
179 * @param [in] filterList List where pattern is added.
180 * @param [in] str Temporary variable (ie, it may be altered)
182 static void AddFilterPattern(vector<FileFilterElement*> *filterList, CString & str)
184 LPCTSTR commentLeader = _T("##"); // Starts comment
187 // Ignore lines beginning with '##'
188 int pos = str.Find(commentLeader);
192 // Find possible comment-separator '<whitespace>##'
193 while (pos > 0 && !_istspace(str[pos - 1]))
194 pos = str.Find(commentLeader, pos);
196 // Remove comment and whitespaces before it
203 const char * errormsg = NULL;
205 char regexString[200] = {0};
210 // For unicode builds, use UTF-8.
211 // Convert pattern to UTF-8 and set option for PCRE to specify UTF-8.
212 regexLen = TransformUcs2ToUtf8((LPCTSTR)str, _tcslen(str),
213 regexString, sizeof(regexString));
214 pcre_opts |= PCRE_UTF8;
216 strcpy(regexString, (LPCTSTR)str);
217 regexLen = strlen(regexString);
219 pcre_opts |= PCRE_CASELESS;
221 pcre *regexp = pcre_compile(regexString, pcre_opts, &errormsg,
225 FileFilterElement *elem = new FileFilterElement();
228 pcre_extra *pe = pcre_study(regexp, 0, &errormsg);
229 elem->pRegExp = regexp;
231 if (pe != NULL && errormsg != NULL)
232 elem->pRegExpExtra = pe;
234 filterList->push_back(elem);
239 * @brief Parse a filter file, and add it to array if valid.
241 * @param [in] szFilePath Path (w/ filename) to file to load.
242 * @param [out] error Error-code if loading failed (returned NULL).
243 * @return Pointer to new filter, or NULL if error (check error code too).
245 FileFilter * FileFilterMgr::LoadFilterFile(LPCTSTR szFilepath, int & error)
248 if (!file.OpenReadOnly(szFilepath))
250 error = FILTER_ERROR_FILEACCESS;
254 file.ReadBom(); // in case it is a Unicode file, let UniMemFile handle BOM
257 SplitFilename(szFilepath, NULL, &fileName, NULL);
258 FileFilter *pfilter = new FileFilter;
259 pfilter->fullpath = szFilepath;
260 pfilter->name = fileName.c_str(); // Filename is the default name
264 BOOL bLinesLeft = TRUE;
267 // Returns false when last line is read
269 bLinesLeft = file.ReadString(tmpLine, &lossy);
270 sLine = tmpLine.c_str();
274 if (0 == _tcsncmp(sLine, _T("name:"), 5))
276 // specifies display name
277 CString str = sLine.Mid(5);
282 else if (0 == _tcsncmp(sLine, _T("desc:"), 5))
284 // specifies display name
285 CString str = sLine.Mid(5);
288 pfilter->description = str;
290 else if (0 == _tcsncmp(sLine, _T("def:"), 4))
293 CString str = sLine.Mid(4);
295 if (str == _T("0") || str == _T("no") || str == _T("exclude"))
296 pfilter->default_include = false;
297 else if (str == _T("1") || str == _T("yes") || str == _T("include"))
298 pfilter->default_include = true;
300 else if (0 == _tcsncmp(sLine, _T("f:"), 2))
303 CString str = sLine.Mid(2);
304 AddFilterPattern(&pfilter->filefilters, str);
306 else if (0 == _tcsncmp(sLine, _T("d:"), 2))
309 CString str = sLine.Mid(2);
310 AddFilterPattern(&pfilter->dirfilters, str);
312 } while (bLinesLeft == TRUE);
318 * @brief Give client back a pointer to the actual filter.
320 * @param [in] szFilterPath Full path to filterfile.
321 * @return Pointer to found filefilter or NULL;
322 * @note We just do a linear search, because this is seldom called
324 FileFilter * FileFilterMgr::GetFilterByPath(LPCTSTR szFilterPath)
326 vector<FileFilter*>::const_iterator iter = m_filters.begin();
327 while (iter != m_filters.end())
329 if ((*iter)->fullpath.CompareNoCase(szFilterPath) == 0)
337 * @brief Test given string against given regexp list.
339 * @param [in] filterList List of regexps to test against.
340 * @param [in] szTest String to test against regexps.
341 * @return TRUE if string passes
342 * @note Matching stops when first match is found.
344 BOOL TestAgainstRegList(const vector<FileFilterElement*> *filterList, LPCTSTR szTest)
346 vector<FileFilterElement*>::const_iterator iter = filterList->begin();
347 while (iter != filterList->end())
349 //const FileFilterElement & elem = filterList.GetNext(pos);
351 char compString[200] = {0};
353 TCHAR * tempName = _tcsdup(szTest); // Create temp copy for conversions
354 TCHAR * cmpStr = _tcsupr(tempName);
357 stringLen = TransformUcs2ToUtf8(cmpStr, _tcslen(cmpStr),
358 compString, sizeof(compString));
360 strcpy(compString, cmpStr);
361 stringLen = strlen(compString);
364 pcre * regexp = (*iter)->pRegExp;
365 pcre_extra * extra = (*iter)->pRegExpExtra;
366 int result = pcre_exec(regexp, extra, compString, stringLen,
380 * @brief Test given filename against filefilter.
382 * Test filename against active filefilter. If matching rule is found
383 * we must first determine type of rule that matched. If we return FALSE
384 * from this function directory scan marks file as skipped.
386 * @param [in] pFilter Pointer to filefilter
387 * @param [in] szFileName Filename to test
388 * @return TRUE if file passes the filter
390 BOOL FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
391 LPCTSTR szFileName) const
395 if (TestAgainstRegList(&pFilter->filefilters, szFileName))
396 return !pFilter->default_include;
397 return pFilter->default_include;
401 * @brief Test given directory name against filefilter.
403 * Test directory name against active filefilter. If matching rule is found
404 * we must first determine type of rule that matched. If we return FALSE
405 * from this function directory scan marks file as skipped.
407 * @param [in] pFilter Pointer to filefilter
408 * @param [in] szDirName Directory name to test
409 * @return TRUE if directory name passes the filter
411 BOOL FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
412 LPCTSTR szDirName) const
416 if (TestAgainstRegList(&pFilter->dirfilters, szDirName))
417 return !pFilter->default_include;
418 return pFilter->default_include;
422 * @brief Return name of filter.
424 * @param [in] i Index of filter.
425 * @return Name of filter in given index.
427 CString FileFilterMgr::GetFilterName(int i) const
429 return m_filters[i]->name;
433 * @brief Return name of filter.
434 * @param [in] pFilter Filter to get name for.
435 * @return Given filter's name.
437 CString FileFilterMgr::GetFilterName(const FileFilter *pFilter) const
439 return pFilter->name;
443 * @brief Return description of filter.
445 * @param [in] i Index of filter.
446 * @return Description of filter in given index.
448 CString FileFilterMgr::GetFilterDesc(int i) const
450 return m_filters[i]->description;
454 * @brief Return description of filter.
455 * @param [in] pFilter Filter to get description for.
456 * @return Given filter's description.
458 CString FileFilterMgr::GetFilterDesc(const FileFilter *pFilter) const
460 return pFilter->description;
464 * @brief Return full path to filter.
466 * @param [in] i Index of filter.
467 * @return Full path of filter in given index.
469 CString FileFilterMgr::GetFilterPath(int i) const
471 return m_filters[i]->fullpath;
475 * @brief Return full path to filter.
477 * @param [in] pFilter Pointer to filter.
478 * @return Full path of filter.
480 CString FileFilterMgr::GetFullpath(FileFilter * pfilter) const
482 return pfilter->fullpath;
486 * @brief Reload filter from disk
488 * Reloads filter from disk. This is done by creating a new one
489 * to substitute for old one.
490 * @param [in] pFilter Pointer to filter to reload.
491 * @return FILTER_OK when succeeds, one of FILTER_RETVALUE values on error.
492 * @note Given filter (pfilter) is freed and must not be used anymore.
493 * @todo Should return new filter.
495 int FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
497 int errorcode = FILTER_OK;
498 FileFilter * newfilter = LoadFilterFile(pfilter->fullpath, errorcode);
500 if (newfilter == NULL)
505 vector<FileFilter*>::iterator iter = m_filters.begin();
506 while (iter != m_filters.end())
508 if (pfilter == (*iter))
511 m_filters.erase(iter);
515 m_filters.push_back(newfilter);
520 * @brief Reload filter from disk.
522 * Reloads filter from disk. This is done by creating a new one
523 * to substitute for old one.
524 * @param [in] szFullPath Full path to filter file to reload.
525 * @return FILTER_OK when succeeds or one of FILTER_RETVALUE values when fails.
527 int FileFilterMgr::ReloadFilterFromDisk(LPCTSTR szFullPath)
529 int errorcode = FILTER_OK;
530 FileFilter * filter = GetFilterByPath(szFullPath);
532 errorcode = ReloadFilterFromDisk(filter);
534 errorcode = FILTER_NOTFOUND;