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"
24 #include <Poco/String.h>
25 #include <Poco/Glob.h>
26 #include <Poco/DirectoryIterator.h>
27 #include <Poco/RegularExpression.h>
28 #include "UnicodeString.h"
29 #include "FileFilter.h"
34 using Poco::DirectoryIterator;
37 using Poco::RegularExpression;
39 static void AddFilterPattern(vector<FileFilterElementPtr> *filterList, String & str);
42 * @brief Destructor, frees all filters.
44 FileFilterMgr::~FileFilterMgr()
50 * @brief Loads filterfile from disk and adds it to filters.
51 * @param [in] szFilterFile Filter file to load.
52 * @return FILTER_OK if succeeded or one of FILTER_RETVALUE values on error.
54 int FileFilterMgr::AddFilter(const String& szFilterFile)
56 int errorcode = FILTER_OK;
57 FileFilter * pFilter = LoadFilterFile(szFilterFile, errorcode);
59 m_filters.push_back(FileFilterPtr(pFilter));
64 * @brief Load all filter files matching pattern from disk into internal filter set.
65 * @param [in] dir Directory from where filters are loaded.
66 * @param [in] szPattern Pattern for filters to load filters, for example "*.flt".
67 * @param [in] szExt File-extension of filter files.
69 void FileFilterMgr::LoadFromDirectory(const String& dir, const String& szPattern, const String& szExt)
71 const std::string u8ext = ucr::toUTF8(szExt);
72 const size_t extlen = u8ext.length();
76 DirectoryIterator it(ucr::toUTF8(dir));
77 DirectoryIterator end;
78 Glob glb(ucr::toUTF8(szPattern));
80 for (; it != end; ++it)
82 if (it->isDirectory())
84 std::string filename = it.name();
85 if (!glb.match(filename))
89 // caller specified a specific extension
90 // (This is really a workaround for brokenness in windows, which
91 // doesn't screen correctly on extension in pattern)
92 std::string ext = filename.substr(filename.length() - extlen);
93 if (icompare(u8ext, ext) != 0)
97 String filterpath = paths_ConcatPath(dir, ucr::toTString(filename));
98 AddFilter(filterpath);
107 * @brief Removes filter from filterlist.
109 * @param [in] szFilterFile Filename of filter to remove.
111 void FileFilterMgr::RemoveFilter(const String& szFilterFile)
113 // Note that m_filters.GetSize can change during loop
114 vector<FileFilterPtr>::iterator iter = m_filters.begin();
115 while (iter != m_filters.end())
117 if (string_compare_nocase((*iter)->fullpath, szFilterFile) == 0)
119 m_filters.erase(iter);
127 * @brief Removes all filters from current list.
129 void FileFilterMgr::DeleteAllFilters()
135 * @brief Add a single pattern (if nonempty & valid) to a pattern list.
137 * @param [in] filterList List where pattern is added.
138 * @param [in] str Temporary variable (ie, it may be altered)
140 static void AddFilterPattern(vector<FileFilterElementPtr> *filterList, String & str)
142 const String& commentLeader = _T("##"); // Starts comment
143 str = string_trim_ws_begin(str);
145 // Ignore lines beginning with '##'
146 size_t pos = str.find(commentLeader);
150 // Find possible comment-separator '<whitespace>##'
151 while (pos != std::string::npos && !(str[pos - 1] == ' ' || str[pos - 1] == '\t'))
152 pos = str.find(commentLeader, pos + 1);
154 // Remove comment and whitespaces before it
155 if (pos != std::string::npos)
156 str = str.substr(0, pos);
157 str = string_trim_ws_end(str);
161 int re_opts = RegularExpression::RE_CASELESS;
162 std::string regexString = ucr::toUTF8(str);
163 re_opts |= RegularExpression::RE_UTF8;
166 filterList->push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts)));
175 * @brief Parse a filter file, and add it to array if valid.
177 * @param [in] szFilePath Path (w/ filename) to file to load.
178 * @param [out] error Error-code if loading failed (returned NULL).
179 * @return Pointer to new filter, or NULL if error (check error code too).
181 FileFilter * FileFilterMgr::LoadFilterFile(const String& szFilepath, int & error)
184 if (!file.OpenReadOnly(szFilepath))
186 error = FILTER_ERROR_FILEACCESS;
190 file.ReadBom(); // in case it is a Unicode file, let UniMemFile handle BOM
193 paths_SplitFilename(szFilepath, NULL, &fileName, NULL);
194 FileFilter *pfilter = new FileFilter;
195 pfilter->fullpath = szFilepath;
196 pfilter->name = fileName; // Filename is the default name
200 bool bLinesLeft = true;
203 // Returns false when last line is read
205 bLinesLeft = file.ReadString(tmpLine, &lossy);
207 sLine = string_trim_ws(sLine);
209 if (0 == sLine.compare(0, 5, _T("name:"), 5))
211 // specifies display name
212 String str = sLine.substr(5);
213 str = string_trim_ws_begin(str);
217 else if (0 == sLine.compare(0, 5, _T("desc:"), 5))
219 // specifies display name
220 String str = sLine.substr(5);
221 str = string_trim_ws_begin(str);
223 pfilter->description = str;
225 else if (0 == sLine.compare(0, 4, _T("def:"), 4))
228 String str = sLine.substr(4);
229 str = string_trim_ws_begin(str);
230 if (str == _T("0") || str == _T("no") || str == _T("exclude"))
231 pfilter->default_include = false;
232 else if (str == _T("1") || str == _T("yes") || str == _T("include"))
233 pfilter->default_include = true;
235 else if (0 == sLine.compare(0, 2, _T("f:"), 2))
238 String str = sLine.substr(2);
239 AddFilterPattern(&pfilter->filefilters, str);
241 else if (0 == sLine.compare(0, 2, _T("d:"), 2))
244 String str = sLine.substr(2);
245 AddFilterPattern(&pfilter->dirfilters, str);
247 } while (bLinesLeft);
253 * @brief Give client back a pointer to the actual filter.
255 * @param [in] szFilterPath Full path to filterfile.
256 * @return Pointer to found filefilter or NULL;
257 * @note We just do a linear search, because this is seldom called
259 FileFilter * FileFilterMgr::GetFilterByPath(const String& szFilterPath)
261 vector<FileFilterPtr>::const_iterator iter = m_filters.begin();
262 while (iter != m_filters.end())
264 if (string_compare_nocase((*iter)->fullpath, szFilterPath) == 0)
265 return (*iter).get();
272 * @brief Test given string against given regexp list.
274 * @param [in] filterList List of regexps to test against.
275 * @param [in] szTest String to test against regexps.
276 * @return true if string passes
277 * @note Matching stops when first match is found.
279 bool TestAgainstRegList(const vector<FileFilterElementPtr> *filterList, const String& szTest)
281 if (filterList->size() == 0)
284 std::string compString;
285 ucr::toUTF8(szTest, compString);
286 vector<FileFilterElementPtr>::const_iterator iter = filterList->begin();
287 while (iter != filterList->end())
289 RegularExpression::Match match;
292 if ((*iter)->regexp.match(compString, 0, match) > 0)
306 * @brief Test given filename against filefilter.
308 * Test filename against active filefilter. If matching rule is found
309 * we must first determine type of rule that matched. If we return false
310 * from this function directory scan marks file as skipped.
312 * @param [in] pFilter Pointer to filefilter
313 * @param [in] szFileName Filename to test
314 * @return true if file passes the filter
316 bool FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
317 const String& szFileName) const
321 if (TestAgainstRegList(&pFilter->filefilters, szFileName))
322 return !pFilter->default_include;
323 return pFilter->default_include;
327 * @brief Test given directory name against filefilter.
329 * Test directory name against active filefilter. If matching rule is found
330 * we must first determine type of rule that matched. If we return false
331 * from this function directory scan marks file as skipped.
333 * @param [in] pFilter Pointer to filefilter
334 * @param [in] szDirName Directory name to test
335 * @return true if directory name passes the filter
337 bool FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
338 const String& szDirName) const
342 if (TestAgainstRegList(&pFilter->dirfilters, szDirName))
343 return !pFilter->default_include;
344 return pFilter->default_include;
348 * @brief Return name of filter.
350 * @param [in] i Index of filter.
351 * @return Name of filter in given index.
353 String FileFilterMgr::GetFilterName(int i) const
355 return m_filters[i]->name;
359 * @brief Return name of filter.
360 * @param [in] pFilter Filter to get name for.
361 * @return Given filter's name.
363 String FileFilterMgr::GetFilterName(const FileFilter *pFilter) const
365 return pFilter->name;
369 * @brief Return description of filter.
371 * @param [in] i Index of filter.
372 * @return Description of filter in given index.
374 String FileFilterMgr::GetFilterDesc(int i) const
376 return m_filters[i]->description;
380 * @brief Return description of filter.
381 * @param [in] pFilter Filter to get description for.
382 * @return Given filter's description.
384 String FileFilterMgr::GetFilterDesc(const FileFilter *pFilter) const
386 return pFilter->description;
390 * @brief Return full path to filter.
392 * @param [in] i Index of filter.
393 * @return Full path of filter in given index.
395 String FileFilterMgr::GetFilterPath(int i) const
397 return m_filters[i]->fullpath;
401 * @brief Return full path to filter.
403 * @param [in] pFilter Pointer to filter.
404 * @return Full path of filter.
406 String FileFilterMgr::GetFullpath(FileFilter * pfilter) const
408 return pfilter->fullpath;
412 * @brief Reload filter from disk
414 * Reloads filter from disk. This is done by creating a new one
415 * to substitute for old one.
416 * @param [in] pFilter Pointer to filter to reload.
417 * @return FILTER_OK when succeeds, one of FILTER_RETVALUE values on error.
418 * @note Given filter (pfilter) is freed and must not be used anymore.
419 * @todo Should return new filter.
421 int FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
423 int errorcode = FILTER_OK;
424 FileFilter * newfilter = LoadFilterFile(pfilter->fullpath, errorcode);
426 if (newfilter == NULL)
431 vector<FileFilterPtr>::iterator iter = m_filters.begin();
432 while (iter != m_filters.end())
434 if (pfilter == (*iter).get())
436 m_filters.erase(iter);
440 m_filters.push_back(FileFilterPtr(newfilter));
445 * @brief Reload filter from disk.
447 * Reloads filter from disk. This is done by creating a new one
448 * to substitute for old one.
449 * @param [in] szFullPath Full path to filter file to reload.
450 * @return FILTER_OK when succeeds or one of FILTER_RETVALUE values when fails.
452 int FileFilterMgr::ReloadFilterFromDisk(const String& szFullPath)
454 int errorcode = FILTER_OK;
455 FileFilter * filter = GetFilterByPath(szFullPath);
457 errorcode = ReloadFilterFromDisk(filter);
459 errorcode = FILTER_NOTFOUND;