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.Add(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 for (int i = 0; i < m_filters.GetSize(); i++)
152 FileFilter * pFilter = m_filters.GetAt(i);
153 if (pFilter->fullpath.CompareNoCase(szFilterFile) == 0)
155 m_filters.RemoveAt(i);
162 * @brief Removes all filters from current list.
164 void FileFilterMgr::DeleteAllFilters()
166 for (int i=0; i<m_filters.GetSize(); ++i)
171 m_filters.RemoveAll();
175 * @brief Add a single pattern (if nonempty & valid) to a pattern list.
177 * @param [in] filterList List where pattern is added.
178 * @param [in] str Temporary variable (ie, it may be altered)
180 static void AddFilterPattern(vector<FileFilterElement*> *filterList, CString & str)
182 LPCTSTR commentLeader = _T("##"); // Starts comment
185 // Ignore lines beginning with '##'
186 int pos = str.Find(commentLeader);
190 // Find possible comment-separator '<whitespace>##'
191 while (pos > 0 && !_istspace(str[pos - 1]))
192 pos = str.Find(commentLeader, pos);
194 // Remove comment and whitespaces before it
201 const char * errormsg = NULL;
203 char regexString[200] = {0};
208 // For unicode builds, use UTF-8.
209 // Convert pattern to UTF-8 and set option for PCRE to specify UTF-8.
210 regexLen = TransformUcs2ToUtf8((LPCTSTR)str, _tcslen(str),
211 regexString, sizeof(regexString));
212 pcre_opts |= PCRE_UTF8;
214 strcpy(regexString, (LPCTSTR)str);
215 regexLen = strlen(regexString);
217 pcre_opts |= PCRE_CASELESS;
219 pcre *regexp = pcre_compile(regexString, pcre_opts, &errormsg,
223 FileFilterElement *elem = new FileFilterElement();
226 pcre_extra *pe = pcre_study(regexp, 0, &errormsg);
227 elem->pRegExp = regexp;
229 if (pe != NULL && errormsg != NULL)
230 elem->pRegExpExtra = pe;
232 filterList->push_back(elem);
237 * @brief Parse a filter file, and add it to array if valid.
239 * @param [in] szFilePath Path (w/ filename) to file to load.
240 * @param [out] error Error-code if loading failed (returned NULL).
241 * @return Pointer to new filter, or NULL if error (check error code too).
243 FileFilter * FileFilterMgr::LoadFilterFile(LPCTSTR szFilepath, int & error)
246 if (!file.OpenReadOnly(szFilepath))
248 error = FILTER_ERROR_FILEACCESS;
252 file.ReadBom(); // in case it is a Unicode file, let UniMemFile handle BOM
255 SplitFilename(szFilepath, NULL, &fileName, NULL);
256 FileFilter *pfilter = new FileFilter;
257 pfilter->fullpath = szFilepath;
258 pfilter->name = fileName.c_str(); // Filename is the default name
262 BOOL bLinesLeft = TRUE;
265 // Returns false when last line is read
266 bLinesLeft = file.ReadString(sLine, &lossy);
270 if (0 == _tcsncmp(sLine, _T("name:"), 5))
272 // specifies display name
273 CString str = sLine.Mid(5);
278 else if (0 == _tcsncmp(sLine, _T("desc:"), 5))
280 // specifies display name
281 CString str = sLine.Mid(5);
284 pfilter->description = str;
286 else if (0 == _tcsncmp(sLine, _T("def:"), 4))
289 CString str = sLine.Mid(4);
291 if (str == _T("0") || str == _T("no") || str == _T("exclude"))
292 pfilter->default_include = false;
293 else if (str == _T("1") || str == _T("yes") || str == _T("include"))
294 pfilter->default_include = true;
296 else if (0 == _tcsncmp(sLine, _T("f:"), 2))
299 CString str = sLine.Mid(2);
300 AddFilterPattern(&pfilter->filefilters, str);
302 else if (0 == _tcsncmp(sLine, _T("d:"), 2))
305 CString str = sLine.Mid(2);
306 AddFilterPattern(&pfilter->dirfilters, str);
308 } while (bLinesLeft == TRUE);
314 * @brief Give client back a pointer to the actual filter.
316 * @param [in] szFilterPath Full path to filterfile.
317 * @return Pointer to found filefilter or NULL;
318 * @note We just do a linear search, because this is seldom called
320 FileFilter * FileFilterMgr::GetFilterByPath(LPCTSTR szFilterPath)
322 for (int i=0; i<m_filters.GetSize(); ++i)
324 if (m_filters[i]->fullpath.CompareNoCase(szFilterPath) == 0)
331 * @brief Test given string against given regexp list.
333 * @param [in] filterList List of regexps to test against.
334 * @param [in] szTest String to test against regexps.
335 * @return TRUE if string passes
336 * @note Matching stops when first match is found.
338 BOOL TestAgainstRegList(const vector<FileFilterElement*> *filterList, LPCTSTR szTest)
340 vector<FileFilterElement*>::const_iterator iter = filterList->begin();
341 while (iter != filterList->end())
343 //const FileFilterElement & elem = filterList.GetNext(pos);
345 char compString[200] = {0};
347 TCHAR * tempName = _tcsdup(szTest); // Create temp copy for conversions
348 TCHAR * cmpStr = _tcsupr(tempName);
351 stringLen = TransformUcs2ToUtf8(cmpStr, _tcslen(cmpStr),
352 compString, sizeof(compString));
354 strcpy(compString, cmpStr);
355 stringLen = strlen(compString);
358 pcre * regexp = (*iter)->pRegExp;
359 pcre_extra * extra = (*iter)->pRegExpExtra;
360 int result = pcre_exec(regexp, extra, compString, stringLen,
374 * @brief Test given filename against filefilter.
376 * Test filename against active filefilter. If matching rule is found
377 * we must first determine type of rule that matched. If we return FALSE
378 * from this function directory scan marks file as skipped.
380 * @param [in] pFilter Pointer to filefilter
381 * @param [in] szFileName Filename to test
382 * @return TRUE if file passes the filter
384 BOOL FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
385 LPCTSTR szFileName) const
389 if (TestAgainstRegList(&pFilter->filefilters, szFileName))
390 return !pFilter->default_include;
391 return pFilter->default_include;
395 * @brief Test given directory name against filefilter.
397 * Test directory name against active filefilter. If matching rule is found
398 * we must first determine type of rule that matched. If we return FALSE
399 * from this function directory scan marks file as skipped.
401 * @param [in] pFilter Pointer to filefilter
402 * @param [in] szDirName Directory name to test
403 * @return TRUE if directory name passes the filter
405 BOOL FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
406 LPCTSTR szDirName) const
410 if (TestAgainstRegList(&pFilter->dirfilters, szDirName))
411 return !pFilter->default_include;
412 return pFilter->default_include;
416 * @brief Return name of filter.
418 * @param [in] i Index of filter.
419 * @return Name of filter in given index.
421 CString FileFilterMgr::GetFilterName(int i) const
423 return m_filters[i]->name;
427 * @brief Return name of filter.
428 * @param [in] pFilter Filter to get name for.
429 * @return Given filter's name.
431 CString FileFilterMgr::GetFilterName(const FileFilter *pFilter) const
433 return pFilter->name;
437 * @brief Return description of filter.
439 * @param [in] i Index of filter.
440 * @return Description of filter in given index.
442 CString FileFilterMgr::GetFilterDesc(int i) const
444 return m_filters[i]->description;
448 * @brief Return description of filter.
449 * @param [in] pFilter Filter to get description for.
450 * @return Given filter's description.
452 CString FileFilterMgr::GetFilterDesc(const FileFilter *pFilter) const
454 return pFilter->description;
458 * @brief Return full path to filter.
460 * @param [in] i Index of filter.
461 * @return Full path of filter in given index.
463 CString FileFilterMgr::GetFilterPath(int i) const
465 return m_filters[i]->fullpath;
469 * @brief Return full path to filter.
471 * @param [in] pFilter Pointer to filter.
472 * @return Full path of filter.
474 CString FileFilterMgr::GetFullpath(FileFilter * pfilter) const
476 return pfilter->fullpath;
480 * @brief Reload filter from disk
482 * Reloads filter from disk. This is done by creating a new one
483 * to substitute for old one.
484 * @param [in] pFilter Pointer to filter to reload.
485 * @return FILTER_OK when succeeds, one of FILTER_RETVALUE values on error.
486 * @note Given filter (pfilter) is freed and must not be used anymore.
487 * @todo Should return new filter.
489 int FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
491 int errorcode = FILTER_OK;
492 FileFilter * newfilter = LoadFilterFile(pfilter->fullpath, errorcode);
494 if (newfilter == NULL)
499 for (int i = 0; i < m_filters.GetSize(); ++i)
501 if (pfilter == m_filters[i])
503 m_filters.RemoveAt(i);
508 m_filters.Add(newfilter);
513 * @brief Reload filter from disk.
515 * Reloads filter from disk. This is done by creating a new one
516 * to substitute for old one.
517 * @param [in] szFullPath Full path to filter file to reload.
518 * @return FILTER_OK when succeeds or one of FILTER_RETVALUE values when fails.
520 int FileFilterMgr::ReloadFilterFromDisk(LPCTSTR szFullPath)
522 int errorcode = FILTER_OK;
523 FileFilter * filter = GetFilterByPath(szFullPath);
525 errorcode = ReloadFilterFromDisk(filter);
527 errorcode = FILTER_NOTFOUND;