1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * @file FileFilterHelper.cpp
5 * @brief Implementation file for FileFilterHelper class
9 #include "FileFilterHelper.h"
10 #include "UnicodeString.h"
11 #include "FilterList.h"
13 #include "FileFilterMgr.h"
15 #include "Environment.h"
21 * @brief Constructor, creates new filtermanager.
23 FileFilterHelper::FileFilterHelper()
24 : m_pMaskFileFilter(nullptr)
25 , m_pMaskDirFilter(nullptr)
27 , m_fileFilterMgr(new FileFilterMgr)
28 , m_currentFilter(nullptr)
33 * @brief Destructor, deletes filtermanager.
35 FileFilterHelper::~FileFilterHelper() = default;
38 * @brief Store current filter path.
40 * Select filter based on filepath. If filter with that path
41 * is found select it. Otherwise set path to empty (default).
42 * @param [in] szFileFilterPath Full path to filter to select.
44 void FileFilterHelper::SetFileFilterPath(const String& szFileFilterPath)
46 // Use none as default path
47 m_sFileFilterPath.clear();
49 if (m_fileFilterMgr == nullptr)
52 // Don't bother to lookup empty path
53 if (!szFileFilterPath.empty())
55 m_currentFilter = m_fileFilterMgr->GetFilterByPath(szFileFilterPath);
56 if (m_currentFilter != nullptr)
57 m_sFileFilterPath = szFileFilterPath;
62 * @brief Get list of filters currently available.
64 * @param [out] selected Filepath of currently selected filter.
65 * @return Filter list to receive found filters.
67 std::vector<FileFilterInfo> FileFilterHelper::GetFileFilters(String & selected) const
69 std::vector<FileFilterInfo> filters;
70 if (m_fileFilterMgr != nullptr)
72 const int count = m_fileFilterMgr->GetFilterCount();
73 filters.reserve(count);
74 for (int i = 0; i < count; ++i)
76 FileFilterInfo filter;
77 filter.fullpath = m_fileFilterMgr->GetFilterPath(i);
78 filter.name = m_fileFilterMgr->GetFilterName(i);
79 filter.description = m_fileFilterMgr->GetFilterDesc(i);
80 filters.push_back(filter);
83 selected = m_sFileFilterPath;
88 * @brief Return name of filter in given file.
89 * If no filter cannot be found, return empty string.
90 * @param [in] filterPath Path to filterfile.
91 * @sa FileFilterHelper::GetFileFilterPath()
93 String FileFilterHelper::GetFileFilterName(const String& filterPath) const
97 vector<FileFilterInfo> filters = GetFileFilters(selected);
98 vector<FileFilterInfo>::const_iterator iter = filters.begin();
99 while (iter != filters.end())
101 if ((*iter).fullpath == filterPath)
112 * @brief Return path to filter with given name.
113 * @param [in] filterName Name of filter.
114 * @sa FileFilterHelper::GetFileFilterName()
116 String FileFilterHelper::GetFileFilterPath(const String& filterName) const
120 vector<FileFilterInfo> filters = GetFileFilters(selected);
121 vector<FileFilterInfo>::const_iterator iter = filters.begin();
122 while (iter != filters.end())
124 if ((*iter).name == filterName)
126 path = (*iter).fullpath;
135 * @brief Set User's filter folder.
136 * @param [in] filterPath Location of User's filters.
138 void FileFilterHelper::SetUserFilterPath(const String & filterPath)
140 m_sUserSelFilterPath = filterPath;
141 paths::normalize(m_sUserSelFilterPath);
145 * @brief Select between mask and filterfile.
146 * @param [in] bUseMask If true we use mask instead of filter files.
148 void FileFilterHelper::UseMask(bool bUseMask)
150 m_bUseMask = bUseMask;
153 if (m_pMaskFileFilter == nullptr)
155 m_pMaskFileFilter.reset(new FilterList);
157 if (m_pMaskDirFilter == nullptr)
159 m_pMaskDirFilter.reset(new FilterList);
164 m_pMaskFileFilter.reset();
165 m_pMaskDirFilter.reset();
170 * @brief Set filemask for filtering.
171 * @param [in] strMask Mask to set (e.g. *.cpp;*.h).
173 void FileFilterHelper::SetMask(const String& strMask)
177 throw "Filter mask tried to set when masks disabled!";
180 auto [regExpFile, regExpFileExclude, regExpDir, regExpDirExclude] = ParseExtensions(strMask);
182 std::string regexp_str_file = ucr::toUTF8(regExpFile);
183 std::string regexp_str_file_excluded = ucr::toUTF8(regExpFileExclude);
184 std::string regexp_str_dir = ucr::toUTF8(regExpDir);
185 std::string regexp_str_dir_excluded = ucr::toUTF8(regExpDirExclude);
187 m_pMaskFileFilter->RemoveAllFilters();
188 m_pMaskFileFilter->AddRegExp(regexp_str_file, false);
189 if (!regexp_str_file_excluded.empty())
190 m_pMaskFileFilter->AddRegExp(regexp_str_file_excluded, true);
191 m_pMaskDirFilter->RemoveAllFilters();
192 m_pMaskDirFilter->AddRegExp(regexp_str_dir, false);
193 if (!regexp_str_dir_excluded.empty())
194 m_pMaskDirFilter->AddRegExp(regexp_str_dir_excluded, true);
198 * @brief Check if any of filefilter rules match to filename.
200 * @param [in] szFileName Filename to test.
201 * @return true unless we're suppressing this file by filter
203 bool FileFilterHelper::includeFile(const String& szFileName) const
207 if (m_pMaskFileFilter == nullptr)
209 throw "Use mask set, but no filter rules for mask!";
212 // preprend a backslash if there is none
213 String strFileName = strutils::makelower(szFileName);
214 if (strFileName.empty() || strFileName[0] != '\\')
215 strFileName = _T("\\") + strFileName;
216 // append a point if there is no extension
217 if (strFileName.find('.') == String::npos)
218 strFileName = strFileName + _T(".");
220 return m_pMaskFileFilter->Match(ucr::toUTF8(strFileName));
224 if (m_fileFilterMgr == nullptr || m_currentFilter ==nullptr)
226 return m_fileFilterMgr->TestFileNameAgainstFilter(m_currentFilter, szFileName);
231 * @brief Check if any of filefilter rules match to directoryname.
233 * @param [in] szFileName Directoryname to test.
234 * @return true unless we're suppressing this directory by filter
236 bool FileFilterHelper::includeDir(const String& szDirName) const
240 if (m_pMaskDirFilter == nullptr)
242 throw "Use mask set, but no filter rules for mask!";
245 // preprend a backslash if there is none
246 String strDirName = strutils::makelower(szDirName);
247 if (strDirName.empty() || strDirName[0] != '\\')
248 strDirName = _T("\\") + strDirName;
249 // append a point if there is no extension
250 if (strDirName.find('.') == String::npos)
251 strDirName = strDirName + _T(".");
253 return m_pMaskDirFilter->Match(ucr::toUTF8(strDirName));
257 if (m_fileFilterMgr == nullptr || m_currentFilter == nullptr)
261 String strDirName(_T("\\"));
262 strDirName += szDirName;
264 return m_fileFilterMgr->TestDirNameAgainstFilter(m_currentFilter, strDirName);
269 * @brief Load in all filters in a folder.
270 * @param [in] dir Folder from where to load filters.
271 * @param [in] sPattern Wildcard defining files to add to map as filter files.
272 * It is filemask, for example, "*.flt"
274 void FileFilterHelper::LoadFileFilterDirPattern(const String& dir, const String& szPattern)
276 m_fileFilterMgr->LoadFromDirectory(dir, szPattern, FileFilterExt);
279 static String ConvertWildcardPatternToRegexp(const String& pattern)
282 for (const TCHAR *p = pattern.c_str(); *p; ++p)
286 case '\\': strRegex += _T("\\\\"); break;
287 case '.': strRegex += _T("\\."); break;
288 case '[': strRegex += _T("\\["); break;
289 case ']': strRegex += _T("\\]"); break;
290 case '(': strRegex += _T("\\("); break;
291 case ')': strRegex += _T("\\)"); break;
292 case '$': strRegex += _T("\\$"); break;
293 case '^': strRegex += _T("\\^"); break;
294 case '+': strRegex += _T("\\+"); break;
295 case '?': strRegex += _T("[^\\\\]"); break;
296 case '*': strRegex += _T("[^\\\\]*"); break;
297 default: strRegex += *p; break;
301 return _T("(^|\\\\)") + strRegex;
305 * @brief Convert user-given extension list to valid regular expression.
306 * @param [in] Extension list/mask to convert to regular expression.
307 * @return Regular expression that matches extension list.
309 std::tuple<String, String, String, String> FileFilterHelper::ParseExtensions(const String &extensions) const
311 String strFileParsed;
313 std::vector<String> filePatterns;
314 std::vector<String> filePatternsExclude;
315 std::vector<String> dirPatterns;
316 std::vector<String> dirPatternsExclude;
317 String ext(extensions);
318 static const TCHAR pszSeps[] = _T(" ;|,:");
320 ext += _T(";"); // Add one separator char to end
321 size_t pos = ext.find_first_of(pszSeps);
323 while (pos != String::npos)
325 String token = ext.substr(0, pos); // Get first extension
326 ext = ext.substr(pos + 1); // Remove extension + separator
328 // Only "*." or "*.something" allowed, other ignored
329 if (token.length() >= 1)
331 bool exclude = token[0] == '!';
333 token = token.substr(1);
334 bool isdir = token.back() == '\\';
336 token = token.substr(0, token.size() - 1);
337 if (token.find('.') == String::npos && !token.empty() && token.back() != '*')
339 String strRegex = strutils::makelower(ConvertWildcardPatternToRegexp(token));
343 dirPatternsExclude.push_back(strRegex);
345 filePatternsExclude.push_back(strRegex);
350 dirPatterns.push_back(strRegex);
352 filePatterns.push_back(strRegex);
356 pos = ext.find_first_of(pszSeps);
359 if (filePatterns.empty())
360 strFileParsed = _T(".*"); // Match everything
362 strFileParsed = strutils::join(filePatterns.begin(), filePatterns.end(), _T("|"));
363 if (dirPatterns.empty())
364 strDirParsed = _T(".*"); // Match everything
366 strDirParsed = strutils::join(dirPatterns.begin(), dirPatterns.end(), _T("|"));
367 String strFileParsedExclude = strutils::join(filePatternsExclude.begin(), filePatternsExclude.end(), _T("|"));
368 String strDirParsedExclude = strutils::join(dirPatternsExclude.begin(), dirPatternsExclude.end(), _T("|"));
369 return { strFileParsed, strFileParsedExclude, strDirParsed, strDirParsedExclude };
373 * @brief Returns active filter (or mask string)
374 * @return The active filter.
376 String FileFilterHelper::GetFilterNameOrMask() const
381 sFilter = GetFileFilterName(m_sFileFilterPath);
391 * Simple-to-use function to select filter. This function determines
392 * filter type so caller doesn't need to care about it.
394 * @param [in] filter File mask or filter name.
395 * @return true if given filter was set, false if default filter was set.
396 * @note If function returns false, you should ask filter set with
397 * GetFilterNameOrMask().
399 bool FileFilterHelper::SetFilter(const String &filter)
401 // If filter is empty string set default filter
406 SetFileFilterPath(_T(""));
410 // Remove leading and trailing whitespace characters from the string.
411 String flt = strutils::trim_ws(filter);
413 String path = GetFileFilterPath(flt);
417 SetFileFilterPath(path);
423 SetFileFilterPath(_T(""));
430 * @brief Reloads changed filter files
432 * Checks if filter file has been modified since it was last time
433 * loaded/reloaded. If file has been modified we reload it.
434 * @todo How to handle an error in reloading filter?
436 void FileFilterHelper::ReloadUpdatedFilters()
440 vector<FileFilterInfo> filters = GetFileFilters(selected);
441 vector<FileFilterInfo>::const_iterator iter = filters.begin();
442 while (iter != filters.end())
444 String path = (*iter).fullpath;
446 fileInfo.Update(path);
447 if (fileInfo.mtime != (*iter).fileinfo.mtime ||
448 fileInfo.size != (*iter).fileinfo.size)
450 // Reload filter after changing it
451 int retval = m_fileFilterMgr->ReloadFilterFromDisk(path);
453 if (retval == FILTER_OK)
455 // If it was active filter we have to re-set it
456 if (path == selected)
457 SetFileFilterPath(path);
465 * @brief Load any known file filters
466 * @todo Preserve filter selection? How?
468 void FileFilterHelper::LoadAllFileFilters()
470 // First delete existing filters
471 m_fileFilterMgr->DeleteAllFilters();
473 // Program application directory
474 m_sGlobalFilterPath = paths::ConcatPath(env::GetProgPath(), _T("Filters"));
475 paths::normalize(m_sGlobalFilterPath);
476 String pattern(_T("*"));
477 pattern += FileFilterExt;
478 LoadFileFilterDirPattern(m_sGlobalFilterPath, pattern);
479 if (strutils::compare_nocase(m_sGlobalFilterPath, m_sUserSelFilterPath) != 0)
480 LoadFileFilterDirPattern(m_sUserSelFilterPath, pattern);
484 * @brief Return path to global filters (& create if needed), or empty if cannot create
486 String FileFilterHelper::GetGlobalFilterPathWithCreate() const
488 return paths::EnsurePathExist(m_sGlobalFilterPath);
492 * @brief Return path to user filters (& create if needed), or empty if cannot create
494 String FileFilterHelper::GetUserFilterPathWithCreate() const
496 return paths::EnsurePathExist(m_sUserSelFilterPath);
500 * @brief Clone file filter helper from another file filter helper.
501 * This function clones file filter helper from another file filter helper.
502 * Current contents in the file filter helper are removed and new contents added from the given file filter helper.
503 * @param [in] pHelper File filter helper to clone.
505 void FileFilterHelper::CloneFrom(const FileFilterHelper* pHelper)
510 if (pHelper->m_pMaskFileFilter)
512 std::unique_ptr<FilterList> filterList(new FilterList());
513 m_pMaskFileFilter = std::move(filterList);
514 m_pMaskFileFilter->CloneFrom(pHelper->m_pMaskFileFilter.get());
517 if (pHelper->m_pMaskDirFilter)
519 std::unique_ptr<FilterList> filterList(new FilterList());
520 m_pMaskDirFilter = std::move(filterList);
521 m_pMaskDirFilter->CloneFrom(pHelper->m_pMaskDirFilter.get());
524 if (pHelper->m_fileFilterMgr)
526 std::unique_ptr<FileFilterMgr> fileFilterMgr(new FileFilterMgr());
527 m_fileFilterMgr = std::move(fileFilterMgr);
528 m_fileFilterMgr->CloneFrom(pHelper->m_fileFilterMgr.get());
531 m_currentFilter = nullptr;
532 if (pHelper->m_currentFilter && pHelper->m_fileFilterMgr)
534 int count = pHelper->m_fileFilterMgr->GetFilterCount();
535 for (int i = 0; i < count; i++)
536 if (pHelper->m_fileFilterMgr->GetFilterByIndex(i) == pHelper->m_currentFilter)
538 m_currentFilter = m_fileFilterMgr->GetFilterByIndex(i);
543 m_sFileFilterPath = pHelper->m_sFileFilterPath;
544 m_sMask = pHelper->m_sMask;
545 m_bUseMask = pHelper->m_bUseMask;
546 m_sGlobalFilterPath = pHelper->m_sGlobalFilterPath;
547 m_sUserSelFilterPath = pHelper->m_sUserSelFilterPath;