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
22 #include "FileFilterMgr.h"
24 #include <Poco/String.h>
25 #include <Poco/Glob.h>
26 #include <Poco/RegularExpression.h>
27 #include "DirTravel.h"
29 #include "UnicodeString.h"
30 #include "FileFilter.h"
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);
58 if (pFilter != nullptr)
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)
73 DirItemArray dirs, files;
74 LoadAndSortFiles(dir, &dirs, &files, false);
75 Glob glb(ucr::toUTF8(szPattern));
77 for (DirItem& item: files)
79 String filename = item.filename;
80 if (!glb.match(ucr::toUTF8(filename)))
84 // caller specified a specific extension
85 // (This is really a workaround for brokenness in windows, which
86 // doesn't screen correctly on extension in pattern)
87 const String ext = filename.substr(filename.length() - szExt.length());
88 if (strutils::compare_nocase(szExt, ext) != 0)
92 String filterpath = paths::ConcatPath(dir, filename);
93 AddFilter(filterpath);
102 * @brief Removes filter from filterlist.
104 * @param [in] szFilterFile Filename of filter to remove.
106 void FileFilterMgr::RemoveFilter(const String& szFilterFile)
108 // Note that m_filters.GetSize can change during loop
109 vector<FileFilterPtr>::iterator iter = m_filters.begin();
110 while (iter != m_filters.end())
112 if (strutils::compare_nocase((*iter)->fullpath, szFilterFile) == 0)
114 m_filters.erase(iter);
122 * @brief Removes all filters from current list.
124 void FileFilterMgr::DeleteAllFilters()
130 * @brief Add a single pattern (if nonempty & valid) to a pattern list.
132 * @param [in] filterList List where pattern is added.
133 * @param [in] str Temporary variable (ie, it may be altered)
135 static void AddFilterPattern(vector<FileFilterElementPtr> *filterList, String & str)
137 const String& commentLeader = _T("##"); // Starts comment
138 str = strutils::trim_ws_begin(str);
140 // Ignore lines beginning with '##'
141 size_t pos = str.find(commentLeader);
145 // Find possible comment-separator '<whitespace>##'
146 while (pos != std::string::npos && !(str[pos - 1] == ' ' || str[pos - 1] == '\t'))
147 pos = str.find(commentLeader, pos + 1);
149 // Remove comment and whitespaces before it
150 if (pos != std::string::npos)
151 str = str.substr(0, pos);
152 str = strutils::trim_ws_end(str);
156 int re_opts = RegularExpression::RE_CASELESS;
157 std::string regexString = ucr::toUTF8(str);
158 re_opts |= RegularExpression::RE_UTF8;
161 filterList->push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts)));
170 * @brief Parse a filter file, and add it to array if valid.
172 * @param [in] szFilePath Path (w/ filename) to file to load.
173 * @param [out] error Error-code if loading failed (returned `nullptr`).
174 * @return Pointer to new filter, or `nullptr` if error (check error code too).
176 FileFilter * FileFilterMgr::LoadFilterFile(const String& szFilepath, int & error)
179 if (!file.OpenReadOnly(szFilepath))
181 error = FILTER_ERROR_FILEACCESS;
185 file.ReadBom(); // in case it is a Unicode file, let UniMemFile handle BOM
188 paths::SplitFilename(szFilepath, nullptr, &fileName, nullptr);
189 FileFilter *pfilter = new FileFilter;
190 pfilter->fullpath = szFilepath;
191 pfilter->name = fileName; // Filename is the default name
195 bool bLinesLeft = true;
198 // Returns false when last line is read
200 bLinesLeft = file.ReadString(tmpLine, &lossy);
202 sLine = strutils::trim_ws(sLine);
204 if (0 == sLine.compare(0, 5, _T("name:"), 5))
206 // specifies display name
207 String str = sLine.substr(5);
208 str = strutils::trim_ws_begin(str);
212 else if (0 == sLine.compare(0, 5, _T("desc:"), 5))
214 // specifies display name
215 String str = sLine.substr(5);
216 str = strutils::trim_ws_begin(str);
218 pfilter->description = str;
220 else if (0 == sLine.compare(0, 4, _T("def:"), 4))
223 String str = sLine.substr(4);
224 str = strutils::trim_ws_begin(str);
225 if (str == _T("0") || str == _T("no") || str == _T("exclude"))
226 pfilter->default_include = false;
227 else if (str == _T("1") || str == _T("yes") || str == _T("include"))
228 pfilter->default_include = true;
230 else if (0 == sLine.compare(0, 2, _T("f:"), 2))
233 String str = sLine.substr(2);
234 AddFilterPattern(&pfilter->filefilters, str);
236 else if (0 == sLine.compare(0, 2, _T("d:"), 2))
239 String str = sLine.substr(2);
240 AddFilterPattern(&pfilter->dirfilters, str);
242 } while (bLinesLeft);
248 * @brief Give client back a pointer to the actual filter.
250 * @param [in] szFilterPath Full path to filterfile.
251 * @return Pointer to found filefilter or `nullptr`;
252 * @note We just do a linear search, because this is seldom called
254 FileFilter * FileFilterMgr::GetFilterByPath(const String& szFilterPath)
256 vector<FileFilterPtr>::const_iterator iter = m_filters.begin();
257 while (iter != m_filters.end())
259 if (strutils::compare_nocase((*iter)->fullpath, szFilterPath) == 0)
260 return (*iter).get();
267 * @brief Test given string against given regexp list.
269 * @param [in] filterList List of regexps to test against.
270 * @param [in] szTest String to test against regexps.
271 * @return true if string passes
272 * @note Matching stops when first match is found.
274 bool TestAgainstRegList(const vector<FileFilterElementPtr> *filterList, const String& szTest)
276 if (filterList->size() == 0)
279 std::string compString;
280 ucr::toUTF8(szTest, compString);
281 vector<FileFilterElementPtr>::const_iterator iter = filterList->begin();
282 while (iter != filterList->end())
284 RegularExpression::Match match;
287 if ((*iter)->regexp.match(compString, 0, match) > 0)
301 * @brief Test given filename against filefilter.
303 * Test filename against active filefilter. If matching rule is found
304 * we must first determine type of rule that matched. If we return false
305 * from this function directory scan marks file as skipped.
307 * @param [in] pFilter Pointer to filefilter
308 * @param [in] szFileName Filename to test
309 * @return true if file passes the filter
311 bool FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
312 const String& szFileName) const
314 if (pFilter == nullptr)
316 if (TestAgainstRegList(&pFilter->filefilters, szFileName))
317 return !pFilter->default_include;
318 return pFilter->default_include;
322 * @brief Test given directory name against filefilter.
324 * Test directory name against active filefilter. If matching rule is found
325 * we must first determine type of rule that matched. If we return false
326 * from this function directory scan marks file as skipped.
328 * @param [in] pFilter Pointer to filefilter
329 * @param [in] szDirName Directory name to test
330 * @return true if directory name passes the filter
332 bool FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
333 const String& szDirName) const
335 if (pFilter == nullptr)
337 if (TestAgainstRegList(&pFilter->dirfilters, szDirName))
338 return !pFilter->default_include;
339 return pFilter->default_include;
343 * @brief Return name of filter.
345 * @param [in] i Index of filter.
346 * @return Name of filter in given index.
348 String FileFilterMgr::GetFilterName(int i) const
350 return m_filters[i]->name;
354 * @brief Return name of filter.
355 * @param [in] pFilter Filter to get name for.
356 * @return Given filter's name.
358 String FileFilterMgr::GetFilterName(const FileFilter *pFilter) const
360 return pFilter->name;
364 * @brief Return description of filter.
366 * @param [in] i Index of filter.
367 * @return Description of filter in given index.
369 String FileFilterMgr::GetFilterDesc(int i) const
371 return m_filters[i]->description;
375 * @brief Return description of filter.
376 * @param [in] pFilter Filter to get description for.
377 * @return Given filter's description.
379 String FileFilterMgr::GetFilterDesc(const FileFilter *pFilter) const
381 return pFilter->description;
385 * @brief Return full path to filter.
387 * @param [in] i Index of filter.
388 * @return Full path of filter in given index.
390 String FileFilterMgr::GetFilterPath(int i) const
392 return m_filters[i]->fullpath;
396 * @brief Return full path to filter.
398 * @param [in] pFilter Pointer to filter.
399 * @return Full path of filter.
401 String FileFilterMgr::GetFullpath(FileFilter * pfilter) const
403 return pfilter->fullpath;
407 * @brief Reload filter from disk
409 * Reloads filter from disk. This is done by creating a new one
410 * to substitute for old one.
411 * @param [in] pFilter Pointer to filter to reload.
412 * @return FILTER_OK when succeeds, one of FILTER_RETVALUE values on error.
413 * @note Given filter (pfilter) is freed and must not be used anymore.
414 * @todo Should return new filter.
416 int FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
418 int errorcode = FILTER_OK;
419 FileFilter * newfilter = LoadFilterFile(pfilter->fullpath, errorcode);
421 if (newfilter == nullptr)
426 vector<FileFilterPtr>::iterator iter = m_filters.begin();
427 while (iter != m_filters.end())
429 if (pfilter == (*iter).get())
431 m_filters.erase(iter);
435 m_filters.push_back(FileFilterPtr(newfilter));
440 * @brief Reload filter from disk.
442 * Reloads filter from disk. This is done by creating a new one
443 * to substitute for old one.
444 * @param [in] szFullPath Full path to filter file to reload.
445 * @return FILTER_OK when succeeds or one of FILTER_RETVALUE values when fails.
447 int FileFilterMgr::ReloadFilterFromDisk(const String& szFullPath)
449 int errorcode = FILTER_OK;
450 FileFilter * filter = GetFilterByPath(szFullPath);
452 errorcode = ReloadFilterFromDisk(filter);
454 errorcode = FILTER_NOTFOUND;