1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * @file FileFilterMgr.cpp
5 * @brief Implementation of FileFilterMgr and supporting routines
9 #include "FileFilterMgr.h"
11 #include <Poco/String.h>
12 #include <Poco/Glob.h>
13 #include <Poco/RegularExpression.h>
14 #include "DirTravel.h"
16 #include "UnicodeString.h"
17 #include "FileFilter.h"
24 using Poco::RegularExpression;
26 static void AddFilterPattern(vector<FileFilterElementPtr> *filterList, String & str);
29 * @brief Destructor, frees all filters.
31 FileFilterMgr::~FileFilterMgr()
37 * @brief Loads filterfile from disk and adds it to filters.
38 * @param [in] szFilterFile Filter file to load.
39 * @return FILTER_OK if succeeded or one of FILTER_RETVALUE values on error.
41 int FileFilterMgr::AddFilter(const String& szFilterFile)
43 int errorcode = FILTER_OK;
44 FileFilter * pFilter = LoadFilterFile(szFilterFile, errorcode);
45 if (pFilter != nullptr)
46 m_filters.push_back(FileFilterPtr(pFilter));
51 * @brief Load all filter files matching pattern from disk into internal filter set.
52 * @param [in] dir Directory from where filters are loaded.
53 * @param [in] szPattern Pattern for filters to load filters, for example "*.flt".
54 * @param [in] szExt File-extension of filter files.
56 void FileFilterMgr::LoadFromDirectory(const String& dir, const String& szPattern, const String& szExt)
60 DirItemArray dirs, files;
61 LoadAndSortFiles(dir, &dirs, &files, false);
62 Glob glb(ucr::toUTF8(szPattern));
64 for (DirItem& item: files)
66 String filename = item.filename;
67 if (!glb.match(ucr::toUTF8(filename)))
71 // caller specified a specific extension
72 // (This is really a workaround for brokenness in windows, which
73 // doesn't screen correctly on extension in pattern)
74 const String ext = filename.substr(filename.length() - szExt.length());
75 if (strutils::compare_nocase(szExt, ext) != 0)
79 String filterpath = paths::ConcatPath(dir, filename);
80 AddFilter(filterpath);
89 * @brief Removes filter from filterlist.
91 * @param [in] szFilterFile Filename of filter to remove.
93 void FileFilterMgr::RemoveFilter(const String& szFilterFile)
95 // Note that m_filters.GetSize can change during loop
96 vector<FileFilterPtr>::iterator iter = m_filters.begin();
97 while (iter != m_filters.end())
99 if (strutils::compare_nocase((*iter)->fullpath, szFilterFile) == 0)
101 m_filters.erase(iter);
109 * @brief Removes all filters from current list.
111 void FileFilterMgr::DeleteAllFilters()
117 * @brief Add a single pattern (if nonempty & valid) to a pattern list.
119 * @param [in] filterList List where pattern is added.
120 * @param [in] str Temporary variable (ie, it may be altered)
122 static void AddFilterPattern(vector<FileFilterElementPtr> *filterList, String & str)
124 const String& commentLeader = _T("##"); // Starts comment
125 str = strutils::trim_ws_begin(str);
127 // Ignore lines beginning with '##'
128 size_t pos = str.find(commentLeader);
132 // Find possible comment-separator '<whitespace>##'
133 while (pos != std::string::npos && !(str[pos - 1] == ' ' || str[pos - 1] == '\t'))
134 pos = str.find(commentLeader, pos + 1);
136 // Remove comment and whitespaces before it
137 if (pos != std::string::npos)
138 str = str.substr(0, pos);
139 str = strutils::trim_ws_end(str);
143 int re_opts = RegularExpression::RE_CASELESS;
144 std::string regexString = ucr::toUTF8(str);
145 re_opts |= RegularExpression::RE_UTF8;
148 filterList->push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts)));
157 * @brief Parse a filter file, and add it to array if valid.
159 * @param [in] szFilePath Path (w/ filename) to file to load.
160 * @param [out] error Error-code if loading failed (returned `nullptr`).
161 * @return Pointer to new filter, or `nullptr` if error (check error code too).
163 FileFilter * FileFilterMgr::LoadFilterFile(const String& szFilepath, int & error)
166 if (!file.OpenReadOnly(szFilepath))
168 error = FILTER_ERROR_FILEACCESS;
172 file.ReadBom(); // in case it is a Unicode file, let UniMemFile handle BOM
175 paths::SplitFilename(szFilepath, nullptr, &fileName, nullptr);
176 FileFilter *pfilter = new FileFilter;
177 pfilter->fullpath = szFilepath;
178 pfilter->name = fileName; // Filename is the default name
182 bool bLinesLeft = true;
185 // Returns false when last line is read
187 bLinesLeft = file.ReadString(tmpLine, &lossy);
189 sLine = strutils::trim_ws(sLine);
191 if (0 == sLine.compare(0, 5, _T("name:"), 5))
193 // specifies display name
194 String str = sLine.substr(5);
195 str = strutils::trim_ws_begin(str);
199 else if (0 == sLine.compare(0, 5, _T("desc:"), 5))
201 // specifies display name
202 String str = sLine.substr(5);
203 str = strutils::trim_ws_begin(str);
205 pfilter->description = str;
207 else if (0 == sLine.compare(0, 4, _T("def:"), 4))
210 String str = sLine.substr(4);
211 str = strutils::trim_ws_begin(str);
212 if (str == _T("0") || str == _T("no") || str == _T("exclude"))
213 pfilter->default_include = false;
214 else if (str == _T("1") || str == _T("yes") || str == _T("include"))
215 pfilter->default_include = true;
217 else if (0 == sLine.compare(0, 2, _T("f:"), 2))
220 String str = sLine.substr(2);
221 AddFilterPattern(&pfilter->filefilters, str);
223 else if (0 == sLine.compare(0, 2, _T("d:"), 2))
226 String str = sLine.substr(2);
227 AddFilterPattern(&pfilter->dirfilters, str);
229 } while (bLinesLeft);
235 * @brief Give client back a pointer to the actual filter.
237 * @param [in] szFilterPath Full path to filterfile.
238 * @return Pointer to found filefilter or `nullptr`;
239 * @note We just do a linear search, because this is seldom called
241 FileFilter * FileFilterMgr::GetFilterByPath(const String& szFilterPath)
243 vector<FileFilterPtr>::const_iterator iter = m_filters.begin();
244 while (iter != m_filters.end())
246 if (strutils::compare_nocase((*iter)->fullpath, szFilterPath) == 0)
247 return (*iter).get();
254 * @brief Test given string against given regexp list.
256 * @param [in] filterList List of regexps to test against.
257 * @param [in] szTest String to test against regexps.
258 * @return true if string passes
259 * @note Matching stops when first match is found.
261 bool TestAgainstRegList(const vector<FileFilterElementPtr> *filterList, const String& szTest)
263 if (filterList->size() == 0)
266 std::string compString;
267 ucr::toUTF8(szTest, compString);
268 vector<FileFilterElementPtr>::const_iterator iter = filterList->begin();
269 while (iter != filterList->end())
271 RegularExpression::Match match;
274 if ((*iter)->regexp.match(compString, 0, match) > 0)
288 * @brief Test given filename against filefilter.
290 * Test filename against active filefilter. If matching rule is found
291 * we must first determine type of rule that matched. If we return false
292 * from this function directory scan marks file as skipped.
294 * @param [in] pFilter Pointer to filefilter
295 * @param [in] szFileName Filename to test
296 * @return true if file passes the filter
298 bool FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
299 const String& szFileName) const
301 if (pFilter == nullptr)
303 if (TestAgainstRegList(&pFilter->filefilters, szFileName))
304 return !pFilter->default_include;
305 return pFilter->default_include;
309 * @brief Test given directory name against filefilter.
311 * Test directory name against active filefilter. If matching rule is found
312 * we must first determine type of rule that matched. If we return false
313 * from this function directory scan marks file as skipped.
315 * @param [in] pFilter Pointer to filefilter
316 * @param [in] szDirName Directory name to test
317 * @return true if directory name passes the filter
319 bool FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
320 const String& szDirName) const
322 if (pFilter == nullptr)
324 if (TestAgainstRegList(&pFilter->dirfilters, szDirName))
325 return !pFilter->default_include;
326 return pFilter->default_include;
330 * @brief Reload filter from disk
332 * Reloads filter from disk. This is done by creating a new one
333 * to substitute for old one.
334 * @param [in] pFilter Pointer to filter to reload.
335 * @return FILTER_OK when succeeds, one of FILTER_RETVALUE values on error.
336 * @note Given filter (pfilter) is freed and must not be used anymore.
337 * @todo Should return new filter.
339 int FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
341 int errorcode = FILTER_OK;
342 FileFilter * newfilter = LoadFilterFile(pfilter->fullpath, errorcode);
344 if (newfilter == nullptr)
349 vector<FileFilterPtr>::iterator iter = m_filters.begin();
350 while (iter != m_filters.end())
352 if (pfilter == (*iter).get())
354 m_filters.erase(iter);
358 m_filters.push_back(FileFilterPtr(newfilter));
363 * @brief Reload filter from disk.
365 * Reloads filter from disk. This is done by creating a new one
366 * to substitute for old one.
367 * @param [in] szFullPath Full path to filter file to reload.
368 * @return FILTER_OK when succeeds or one of FILTER_RETVALUE values when fails.
370 int FileFilterMgr::ReloadFilterFromDisk(const String& szFullPath)
372 int errorcode = FILTER_OK;
373 FileFilter * filter = GetFilterByPath(szFullPath);
375 errorcode = ReloadFilterFromDisk(filter);
377 errorcode = FILTER_NOTFOUND;