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 // RCS ID line follows -- this is updated by CVS
29 #include "FileFilterMgr.h"
31 #include "coretools.h"
37 static char THIS_FILE[] = __FILE__;
41 * @brief Deletes items from filter list.
43 * @param [in] filterList List to empty.
45 void EmptyFilterList(FileFilterList & filterList)
47 while (!filterList.IsEmpty())
49 FileFilterElement &elem = filterList.GetHead();
50 pcre_free(elem.pRegExp);
51 pcre_free(elem.pRegExpExtra);
52 filterList.RemoveHead();
57 * @brief One actual filter.
59 * For example, this might be a GNU C filter, excluding *.o files and CVS
60 * directories. That is to say, a filter is a set of file masks and
61 * directory masks. Usually FileFilter contains rules from one filter
62 * definition file. So it can be thought as filter file contents.
67 bool default_include; /**< If true, filter rules are inclusive by default */
68 CString name; /**< Filter name (shown in UI) */
69 CString description; /**< Filter description text */
70 CString fullpath; /**< Full path to filter file */
71 FileFilterList filefilters; /**< List of rules for files */
72 FileFilterList dirfilters; /**< List of rules for directories */
73 FileFilter() : default_include(true) { }
77 FileFilter::~FileFilter()
79 EmptyFilterList(filefilters);
80 EmptyFilterList(dirfilters);
83 FileFilterMgr::~FileFilterMgr()
89 * @brief Loads filterfile from disk and adds it to filters.
90 * @param [in] szFilterFile to load.
91 * @return FILTER_OK if succeeded or one of FILTER_RETVALUE values on error.
93 int FileFilterMgr::AddFilter(LPCTSTR szFilterFile)
95 int errorcode = FILTER_OK;
96 FileFilter * pFilter = LoadFilterFile(szFilterFile, errorcode);
98 m_filters.Add(pFilter);
103 * @brief Load all filter files matching pattern from disk into internal filter set.
105 * @param [in] szPattern Pattern from where to load filters, for example "\\Filters\\*.flt"
106 * @param [in] szExt File-extension of filter files
108 void FileFilterMgr::LoadFromDirectory(LPCTSTR szPattern, LPCTSTR szExt)
111 BOOL bWorking = finder.FindFile(szPattern);
112 int extlen = szExt ? _tcslen(szExt) : 0;
115 bWorking = finder.FindNextFile();
116 if (finder.IsDots() || finder.IsDirectory())
118 CString sFilename = finder.GetFileName();
121 // caller specified a specific extension
122 // (This is really a workaround for brokenness in windows, which
123 // doesn't screen correctly on extension in pattern)
124 if (sFilename.Right(extlen).CompareNoCase(szExt))
127 AddFilter(finder.GetFilePath());
132 * @brief Removes filter from filterlist.
134 * @param [in] szFilterFile Filename of filter to remove.
136 void FileFilterMgr::RemoveFilter(LPCTSTR szFilterFile)
138 // Note that m_filters.GetSize can change during loop
139 for (int i = 0; i < m_filters.GetSize(); i++)
141 FileFilter * pFilter = m_filters.GetAt(i);
142 if (pFilter->fullpath.CompareNoCase(szFilterFile) == 0)
144 m_filters.RemoveAt(i);
151 * @brief Removes all filters from current list.
153 void FileFilterMgr::DeleteAllFilters()
155 for (int i=0; i<m_filters.GetSize(); ++i)
160 m_filters.RemoveAll();
164 * @brief Add a single pattern (if nonempty & valid) to a pattern list.
166 * @param [in] filterList List where pattern is added.
167 * @param [in] str Temporary variable (ie, it may be altered)
169 static void AddFilterPattern(FileFilterList & filterList, CString & str)
171 LPCTSTR commentLeader = _T("##"); // Starts comment
174 // Ignore lines beginning with '##'
175 int pos = str.Find(commentLeader);
179 // Find possible comment-separator '<whitespace>##'
180 while (pos > 0 && !_istspace(str[pos - 1]))
181 pos = str.Find(commentLeader, pos);
183 // Remove comment and whitespaces before it
190 const char * errormsg = NULL;
192 char regexString[200] = {0};
197 // For unicode builds, use UTF-8.
198 // Convert pattern to UTF-8 and set option for PCRE to specify UTF-8.
199 regexLen = TransformUcs2ToUtf8((LPCTSTR)str, _tcslen(str),
200 regexString, sizeof(regexString));
201 pcre_opts |= PCRE_UTF8;
203 strcpy(regexString, (LPCTSTR)str);
204 regexLen = strlen(regexString);
206 pcre_opts |= PCRE_CASELESS;
208 pcre *regexp = pcre_compile(regexString, pcre_opts, &errormsg,
212 FileFilterElement elem;
215 pcre_extra *pe = pcre_study(regexp, 0, &errormsg);
216 elem.pRegExp = regexp;
218 if (pe != NULL && errormsg != NULL)
219 elem.pRegExpExtra = pe;
221 filterList.AddTail(elem);
226 * @brief Parse a filter file, and add it to array if valid.
228 * @param [in] szFilePath Path (w/ filename) to file to load.
229 * @param [out] error Error-code if loading failed (returned NULL).
230 * @return Pointer to new filter, or NULL if error (check error code too).
232 FileFilter * FileFilterMgr::LoadFilterFile(LPCTSTR szFilepath, int & error)
235 if (!file.OpenReadOnly(szFilepath))
237 error = FILTER_ERROR_FILEACCESS;
241 file.ReadBom(); // in case it is a Unicode file, let UniMemFile handle BOM
244 SplitFilename(szFilepath, NULL, &fileName, NULL);
245 FileFilter *pfilter = new FileFilter;
246 pfilter->fullpath = szFilepath;
247 pfilter->name = fileName; // Filename is the default name
251 BOOL bLinesLeft = TRUE;
254 // Returns false when last line is read
255 bLinesLeft = file.ReadString(sLine, &lossy);
259 if (0 == _tcsncmp(sLine, _T("name:"), 5))
261 // specifies display name
262 CString str = sLine.Mid(5);
267 else if (0 == _tcsncmp(sLine, _T("desc:"), 5))
269 // specifies display name
270 CString str = sLine.Mid(5);
273 pfilter->description = str;
275 else if (0 == _tcsncmp(sLine, _T("def:"), 4))
278 CString str = sLine.Mid(4);
280 if (str == _T("0") || str == _T("no") || str == _T("exclude"))
281 pfilter->default_include = false;
282 else if (str == _T("1") || str == _T("yes") || str == _T("include"))
283 pfilter->default_include = true;
285 else if (0 == _tcsncmp(sLine, _T("f:"), 2))
288 CString str = sLine.Mid(2);
289 AddFilterPattern(pfilter->filefilters, str);
291 else if (0 == _tcsncmp(sLine, _T("d:"), 2))
294 CString str = sLine.Mid(2);
295 AddFilterPattern(pfilter->dirfilters, str);
297 } while (bLinesLeft == TRUE);
303 * @brief Give client back a pointer to the actual filter.
305 * @param [in] szFilterPath Full path to filterfile.
306 * @return Pointer to found filefilter or NULL;
307 * @note We just do a linear search, because this is seldom called
309 FileFilter * FileFilterMgr::GetFilterByPath(LPCTSTR szFilterPath)
311 for (int i=0; i<m_filters.GetSize(); ++i)
313 if (m_filters[i]->fullpath.CompareNoCase(szFilterPath) == 0)
320 * @brief Test given string against given regexp list.
322 * @param [in] filterList List of regexps to test against.
323 * @param [in] szTest String to test against regexps.
324 * @return TRUE if string passes
325 * @note Matching stops when first match is found.
327 BOOL TestAgainstRegList(const FileFilterList & filterList, LPCTSTR szTest)
329 for (POSITION pos = filterList.GetHeadPosition(); pos; )
331 const FileFilterElement & elem = filterList.GetNext(pos);
333 char compString[200] = {0};
335 TCHAR * tempName = _tcsdup(szTest); // Create temp copy for conversions
336 TCHAR * cmpStr = _tcsupr(tempName);
339 stringLen = TransformUcs2ToUtf8(cmpStr, _tcslen(cmpStr),
340 compString, sizeof(compString));
342 strcpy(compString, cmpStr);
343 stringLen = strlen(compString);
346 pcre * regexp = elem.pRegExp;
347 pcre_extra * extra = elem.pRegExpExtra;
348 int result = pcre_exec(regexp, extra, compString, stringLen,
360 * @brief Test given filename against filefilter.
362 * Test filename against active filefilter. If matching rule is found
363 * we must first determine type of rule that matched. If we return FALSE
364 * from this function directory scan marks file as skipped.
366 * @param [in] pFilter Pointer to filefilter
367 * @param [in] szFileName Filename to test
368 * @return TRUE if file passes the filter
370 BOOL FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
371 LPCTSTR szFileName) const
375 if (TestAgainstRegList(pFilter->filefilters, szFileName))
376 return !pFilter->default_include;
377 return pFilter->default_include;
381 * @brief Test given directory name against filefilter.
383 * Test directory name against active filefilter. If matching rule is found
384 * we must first determine type of rule that matched. If we return FALSE
385 * from this function directory scan marks file as skipped.
387 * @param [in] pFilter Pointer to filefilter
388 * @param [in] szDirName Directory name to test
389 * @return TRUE if directory name passes the filter
391 BOOL FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
392 LPCTSTR szDirName) const
396 if (TestAgainstRegList(pFilter->dirfilters, szDirName))
397 return !pFilter->default_include;
398 return pFilter->default_include;
402 * @brief Return name of filter.
404 * @param [in] i Index of filter.
405 * @return Name of filter in given index.
407 CString FileFilterMgr::GetFilterName(int i) const
409 return m_filters[i]->name;
412 /** @brief Return name of filter. */
413 CString FileFilterMgr::GetFilterName(const FileFilter *pFilter) const
415 return pFilter->name;
419 * @brief Return description of filter.
421 * @param [in] i Index of filter.
422 * @return Description of filter in given index.
424 CString FileFilterMgr::GetFilterDesc(int i) const
426 return m_filters[i]->description;
429 /** @brief Return description of filter. */
430 CString FileFilterMgr::GetFilterDesc(const FileFilter *pFilter) const
432 return pFilter->description;
436 * @brief Return full path to filter.
438 * @param [in] i Index of filter.
439 * @return Full path of filter in given index.
441 CString FileFilterMgr::GetFilterPath(int i) const
443 return m_filters[i]->fullpath;
447 * @brief Return full path to filter.
449 * @param [in] pFilter Pointer to filter.
450 * @return Full path of filter.
452 CString FileFilterMgr::GetFullpath(FileFilter * pfilter) const
454 return pfilter->fullpath;
458 * @brief Reload filter from disk
460 * Reloads filter from disk. This is done by creating a new one
461 * to substitute for old one.
462 * @param [in] pFilter Pointer to filter to reload.
463 * @return FILTER_OK when succeeds, one of FILTER_RETVALUE values on error.
464 * @note Given filter (pfilter) is freed and must not be used anymore.
465 * @todo Should return new filter.
467 int FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
469 int errorcode = FILTER_OK;
470 FileFilter * newfilter = LoadFilterFile(pfilter->fullpath, errorcode);
472 if (newfilter == NULL)
477 for (int i = 0; i < m_filters.GetSize(); ++i)
479 if (pfilter == m_filters[i])
481 m_filters.RemoveAt(i);
486 m_filters.Add(newfilter);
491 * @brief Reload filter from disk.
493 * Reloads filter from disk. This is done by creating a new one
494 * to substitute for old one.
495 * @param [in] szFullPath Full path to filter file to reload.
496 * @return FILTER_OK when succeeds or one of FILTER_RETVALUE values when fails.
498 int FileFilterMgr::ReloadFilterFromDisk(LPCTSTR szFullPath)
500 int errorcode = FILTER_OK;
501 FileFilter * filter = GetFilterByPath(szFullPath);
503 errorcode = ReloadFilterFromDisk(filter);
505 errorcode = FILTER_NOTFOUND;