1 /////////////////////////////////////////////////////////////////////////////
3 // This program is free software; you can redistribute it and/or modify it
4 // under the terms of the GNU General Public License as published by the
5 // Free Software Foundation; either version 2 of the License, or (at your
6 // option) any later version.
7 // This program is distributed in the hope that it will be useful, but
8 // WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 // General Public License for more details.
11 // You should have received a copy of the GNU General Public License
12 // along with this program; if not, write to the Free Software
13 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
14 /////////////////////////////////////////////////////////////////////////////
16 * @file FileFilterMgr.cpp
18 * @brief Implementation of FileFilterMgr and supporting routines
21 #include "FileFilterMgr.h"
23 #include <Poco/String.h>
24 #include <Poco/Glob.h>
25 #include <Poco/DirectoryIterator.h>
26 #include <Poco/RegularExpression.h>
27 #include "UnicodeString.h"
28 #include "FileFilter.h"
33 using Poco::DirectoryIterator;
36 using Poco::RegularExpression;
38 static void AddFilterPattern(vector<FileFilterElementPtr> *filterList, String & str);
41 * @brief Destructor, frees all filters.
43 FileFilterMgr::~FileFilterMgr()
49 * @brief Loads filterfile from disk and adds it to filters.
50 * @param [in] szFilterFile Filter file to load.
51 * @return FILTER_OK if succeeded or one of FILTER_RETVALUE values on error.
53 int FileFilterMgr::AddFilter(const String& szFilterFile)
55 int errorcode = FILTER_OK;
56 FileFilter * pFilter = LoadFilterFile(szFilterFile, errorcode);
58 m_filters.push_back(FileFilterPtr(pFilter));
63 * @brief Load all filter files matching pattern from disk into internal filter set.
64 * @param [in] dir Directory from where filters are loaded.
65 * @param [in] szPattern Pattern for filters to load filters, for example "*.flt".
66 * @param [in] szExt File-extension of filter files.
68 void FileFilterMgr::LoadFromDirectory(const String& dir, const String& szPattern, const String& szExt)
70 const std::string u8ext = ucr::toUTF8(szExt);
71 const size_t extlen = u8ext.length();
75 DirectoryIterator it(ucr::toUTF8(dir));
76 DirectoryIterator end;
77 Glob glb(ucr::toUTF8(szPattern));
79 for (; it != end; ++it)
81 if (it->isDirectory())
83 std::string filename = it.name();
84 if (!glb.match(filename))
88 // caller specified a specific extension
89 // (This is really a workaround for brokenness in windows, which
90 // doesn't screen correctly on extension in pattern)
91 const std::string ext = filename.substr(filename.length() - extlen);
92 if (icompare(u8ext, ext) != 0)
96 String filterpath = paths::ConcatPath(dir, ucr::toTString(filename));
97 AddFilter(filterpath);
106 * @brief Removes filter from filterlist.
108 * @param [in] szFilterFile Filename of filter to remove.
110 void FileFilterMgr::RemoveFilter(const String& szFilterFile)
112 // Note that m_filters.GetSize can change during loop
113 vector<FileFilterPtr>::iterator iter = m_filters.begin();
114 while (iter != m_filters.end())
116 if (strutils::compare_nocase((*iter)->fullpath, szFilterFile) == 0)
118 m_filters.erase(iter);
126 * @brief Removes all filters from current list.
128 void FileFilterMgr::DeleteAllFilters()
134 * @brief Add a single pattern (if nonempty & valid) to a pattern list.
136 * @param [in] filterList List where pattern is added.
137 * @param [in] str Temporary variable (ie, it may be altered)
139 static void AddFilterPattern(vector<FileFilterElementPtr> *filterList, String & str)
141 const String& commentLeader = _T("##"); // Starts comment
142 str = strutils::trim_ws_begin(str);
144 // Ignore lines beginning with '##'
145 size_t pos = str.find(commentLeader);
149 // Find possible comment-separator '<whitespace>##'
150 while (pos != std::string::npos && !(str[pos - 1] == ' ' || str[pos - 1] == '\t'))
151 pos = str.find(commentLeader, pos + 1);
153 // Remove comment and whitespaces before it
154 if (pos != std::string::npos)
155 str = str.substr(0, pos);
156 str = strutils::trim_ws_end(str);
160 int re_opts = RegularExpression::RE_CASELESS;
161 std::string regexString = ucr::toUTF8(str);
162 re_opts |= RegularExpression::RE_UTF8;
165 filterList->push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts)));
174 * @brief Parse a filter file, and add it to array if valid.
176 * @param [in] szFilePath Path (w/ filename) to file to load.
177 * @param [out] error Error-code if loading failed (returned NULL).
178 * @return Pointer to new filter, or NULL if error (check error code too).
180 FileFilter * FileFilterMgr::LoadFilterFile(const String& szFilepath, int & error)
183 if (!file.OpenReadOnly(szFilepath))
185 error = FILTER_ERROR_FILEACCESS;
189 file.ReadBom(); // in case it is a Unicode file, let UniMemFile handle BOM
192 paths::SplitFilename(szFilepath, NULL, &fileName, NULL);
193 FileFilter *pfilter = new FileFilter;
194 pfilter->fullpath = szFilepath;
195 pfilter->name = fileName; // Filename is the default name
199 bool bLinesLeft = true;
202 // Returns false when last line is read
204 bLinesLeft = file.ReadString(tmpLine, &lossy);
206 sLine = strutils::trim_ws(sLine);
208 if (0 == sLine.compare(0, 5, _T("name:"), 5))
210 // specifies display name
211 String str = sLine.substr(5);
212 str = strutils::trim_ws_begin(str);
216 else if (0 == sLine.compare(0, 5, _T("desc:"), 5))
218 // specifies display name
219 String str = sLine.substr(5);
220 str = strutils::trim_ws_begin(str);
222 pfilter->description = str;
224 else if (0 == sLine.compare(0, 4, _T("def:"), 4))
227 String str = sLine.substr(4);
228 str = strutils::trim_ws_begin(str);
229 if (str == _T("0") || str == _T("no") || str == _T("exclude"))
230 pfilter->default_include = false;
231 else if (str == _T("1") || str == _T("yes") || str == _T("include"))
232 pfilter->default_include = true;
234 else if (0 == sLine.compare(0, 2, _T("f:"), 2))
237 String str = sLine.substr(2);
238 AddFilterPattern(&pfilter->filefilters, str);
240 else if (0 == sLine.compare(0, 2, _T("d:"), 2))
243 String str = sLine.substr(2);
244 AddFilterPattern(&pfilter->dirfilters, str);
246 } while (bLinesLeft);
252 * @brief Give client back a pointer to the actual filter.
254 * @param [in] szFilterPath Full path to filterfile.
255 * @return Pointer to found filefilter or NULL;
256 * @note We just do a linear search, because this is seldom called
258 FileFilter * FileFilterMgr::GetFilterByPath(const String& szFilterPath)
260 vector<FileFilterPtr>::const_iterator iter = m_filters.begin();
261 while (iter != m_filters.end())
263 if (strutils::compare_nocase((*iter)->fullpath, szFilterPath) == 0)
264 return (*iter).get();
271 * @brief Test given string against given regexp list.
273 * @param [in] filterList List of regexps to test against.
274 * @param [in] szTest String to test against regexps.
275 * @return true if string passes
276 * @note Matching stops when first match is found.
278 bool TestAgainstRegList(const vector<FileFilterElementPtr> *filterList, const String& szTest)
280 if (filterList->size() == 0)
283 std::string compString;
284 ucr::toUTF8(szTest, compString);
285 vector<FileFilterElementPtr>::const_iterator iter = filterList->begin();
286 while (iter != filterList->end())
288 RegularExpression::Match match;
291 if ((*iter)->regexp.match(compString, 0, match) > 0)
305 * @brief Test given filename against filefilter.
307 * Test filename against active filefilter. If matching rule is found
308 * we must first determine type of rule that matched. If we return false
309 * from this function directory scan marks file as skipped.
311 * @param [in] pFilter Pointer to filefilter
312 * @param [in] szFileName Filename to test
313 * @return true if file passes the filter
315 bool FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
316 const String& szFileName) const
320 if (TestAgainstRegList(&pFilter->filefilters, szFileName))
321 return !pFilter->default_include;
322 return pFilter->default_include;
326 * @brief Test given directory name against filefilter.
328 * Test directory name against active filefilter. If matching rule is found
329 * we must first determine type of rule that matched. If we return false
330 * from this function directory scan marks file as skipped.
332 * @param [in] pFilter Pointer to filefilter
333 * @param [in] szDirName Directory name to test
334 * @return true if directory name passes the filter
336 bool FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
337 const String& szDirName) const
341 if (TestAgainstRegList(&pFilter->dirfilters, szDirName))
342 return !pFilter->default_include;
343 return pFilter->default_include;
347 * @brief Return name of filter.
349 * @param [in] i Index of filter.
350 * @return Name of filter in given index.
352 String FileFilterMgr::GetFilterName(int i) const
354 return m_filters[i]->name;
358 * @brief Return name of filter.
359 * @param [in] pFilter Filter to get name for.
360 * @return Given filter's name.
362 String FileFilterMgr::GetFilterName(const FileFilter *pFilter) const
364 return pFilter->name;
368 * @brief Return description of filter.
370 * @param [in] i Index of filter.
371 * @return Description of filter in given index.
373 String FileFilterMgr::GetFilterDesc(int i) const
375 return m_filters[i]->description;
379 * @brief Return description of filter.
380 * @param [in] pFilter Filter to get description for.
381 * @return Given filter's description.
383 String FileFilterMgr::GetFilterDesc(const FileFilter *pFilter) const
385 return pFilter->description;
389 * @brief Return full path to filter.
391 * @param [in] i Index of filter.
392 * @return Full path of filter in given index.
394 String FileFilterMgr::GetFilterPath(int i) const
396 return m_filters[i]->fullpath;
400 * @brief Return full path to filter.
402 * @param [in] pFilter Pointer to filter.
403 * @return Full path of filter.
405 String FileFilterMgr::GetFullpath(FileFilter * pfilter) const
407 return pfilter->fullpath;
411 * @brief Reload filter from disk
413 * Reloads filter from disk. This is done by creating a new one
414 * to substitute for old one.
415 * @param [in] pFilter Pointer to filter to reload.
416 * @return FILTER_OK when succeeds, one of FILTER_RETVALUE values on error.
417 * @note Given filter (pfilter) is freed and must not be used anymore.
418 * @todo Should return new filter.
420 int FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
422 int errorcode = FILTER_OK;
423 FileFilter * newfilter = LoadFilterFile(pfilter->fullpath, errorcode);
425 if (newfilter == NULL)
430 vector<FileFilterPtr>::iterator iter = m_filters.begin();
431 while (iter != m_filters.end())
433 if (pfilter == (*iter).get())
435 m_filters.erase(iter);
439 m_filters.push_back(FileFilterPtr(newfilter));
444 * @brief Reload filter from disk.
446 * Reloads filter from disk. This is done by creating a new one
447 * to substitute for old one.
448 * @param [in] szFullPath Full path to filter file to reload.
449 * @return FILTER_OK when succeeds or one of FILTER_RETVALUE values when fails.
451 int FileFilterMgr::ReloadFilterFromDisk(const String& szFullPath)
453 int errorcode = FILTER_OK;
454 FileFilter * filter = GetFilterByPath(szFullPath);
456 errorcode = ReloadFilterFromDisk(filter);
458 errorcode = FILTER_NOTFOUND;