OSDN Git Service

Merge pull request #44 from GreyMerlin/master
[winmerge-jp/winmerge-jp.git] / Src / FileFilterMgr.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    License (GPLv2+):
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 /////////////////////////////////////////////////////////////////////////////
15 /**
16  *  @file FileFilterMgr.cpp
17  *
18  *  @brief Implementation of FileFilterMgr and supporting routines
19  */ 
20
21 #include "FileFilterMgr.h"
22 #include <vector>
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"
29 #include "UniFile.h"
30 #include "paths.h"
31
32 using std::vector;
33 using Poco::DirectoryIterator;
34 using Poco::Glob;
35 using Poco::icompare;
36 using Poco::RegularExpression;
37
38 static void AddFilterPattern(vector<FileFilterElementPtr> *filterList, String & str);
39
40 /**
41  * @brief Destructor, frees all filters.
42  */
43 FileFilterMgr::~FileFilterMgr()
44 {
45         DeleteAllFilters();
46 }
47
48 /**
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.
52  */
53 int FileFilterMgr::AddFilter(const String& szFilterFile)
54 {
55         int errorcode = FILTER_OK;
56         FileFilter * pFilter = LoadFilterFile(szFilterFile, errorcode);
57         if (pFilter)
58                 m_filters.push_back(FileFilterPtr(pFilter));
59         return errorcode;
60 }
61
62 /**
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.
67  */
68 void FileFilterMgr::LoadFromDirectory(const String& dir, const String& szPattern, const String& szExt)
69 {
70         const std::string u8ext = ucr::toUTF8(szExt);
71         const size_t extlen = u8ext.length();
72
73         try
74         {
75                 DirectoryIterator it(ucr::toUTF8(dir));
76                 DirectoryIterator end;
77                 Glob glb(ucr::toUTF8(szPattern));
78         
79                 for (; it != end; ++it)
80                 {
81                         if (it->isDirectory())
82                                 continue;
83                         std::string filename = it.name();
84                         if (!glb.match(filename))
85                                 continue;
86                         if (extlen)
87                         {
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)
93                                         return;
94                         }
95
96                         String filterpath = paths::ConcatPath(dir, ucr::toTString(filename));
97                         AddFilter(filterpath);
98                 }
99         }
100         catch (...)
101         {
102         }
103 }
104
105 /**
106  * @brief Removes filter from filterlist.
107  *
108  * @param [in] szFilterFile Filename of filter to remove.
109  */
110 void FileFilterMgr::RemoveFilter(const String& szFilterFile)
111 {
112         // Note that m_filters.GetSize can change during loop
113         vector<FileFilterPtr>::iterator iter = m_filters.begin();
114         while (iter != m_filters.end())
115         {
116                 if (strutils::compare_nocase((*iter)->fullpath, szFilterFile) == 0)
117                 {
118                         m_filters.erase(iter);
119                         break;
120                 }
121                 ++iter;
122         }
123 }
124
125 /**
126  * @brief Removes all filters from current list.
127  */
128 void FileFilterMgr::DeleteAllFilters()
129 {
130         m_filters.clear();
131 }
132
133 /**
134  * @brief Add a single pattern (if nonempty & valid) to a pattern list.
135  *
136  * @param [in] filterList List where pattern is added.
137  * @param [in] str Temporary variable (ie, it may be altered)
138  */
139 static void AddFilterPattern(vector<FileFilterElementPtr> *filterList, String & str)
140 {
141         const String& commentLeader = _T("##"); // Starts comment
142         str = strutils::trim_ws_begin(str);
143
144         // Ignore lines beginning with '##'
145         size_t pos = str.find(commentLeader);
146         if (pos == 0)
147                 return;
148
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);
152
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);
157         if (str.empty())
158                 return;
159
160         int re_opts = RegularExpression::RE_CASELESS;
161         std::string regexString = ucr::toUTF8(str);
162         re_opts |= RegularExpression::RE_UTF8;
163         try
164         {
165                 filterList->push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts)));
166         }
167         catch (...)
168         {
169                 // TODO:
170         }
171 }
172
173 /**
174  * @brief Parse a filter file, and add it to array if valid.
175  *
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).
179  */
180 FileFilter * FileFilterMgr::LoadFilterFile(const String& szFilepath, int & error)
181 {
182         UniMemFile file;
183         if (!file.OpenReadOnly(szFilepath))
184         {
185                 error = FILTER_ERROR_FILEACCESS;
186                 return NULL;
187         }
188
189         file.ReadBom(); // in case it is a Unicode file, let UniMemFile handle BOM
190
191         String fileName;
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
196
197         String sLine;
198         bool lossy = false;
199         bool bLinesLeft = true;
200         do
201         {
202                 // Returns false when last line is read
203                 String tmpLine;
204                 bLinesLeft = file.ReadString(tmpLine, &lossy);
205                 sLine = tmpLine;
206                 sLine = strutils::trim_ws(sLine);
207
208                 if (0 == sLine.compare(0, 5, _T("name:"), 5))
209                 {
210                         // specifies display name
211                         String str = sLine.substr(5);
212                         str = strutils::trim_ws_begin(str);
213                         if (!str.empty())
214                                 pfilter->name = str;
215                 }
216                 else if (0 == sLine.compare(0, 5, _T("desc:"), 5))
217                 {
218                         // specifies display name
219                         String str = sLine.substr(5);
220                         str = strutils::trim_ws_begin(str);
221                         if (!str.empty())
222                                 pfilter->description = str;
223                 }
224                 else if (0 == sLine.compare(0, 4, _T("def:"), 4))
225                 {
226                         // specifies default
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;
233                 }
234                 else if (0 == sLine.compare(0, 2, _T("f:"), 2))
235                 {
236                         // file filter
237                         String str = sLine.substr(2);
238                         AddFilterPattern(&pfilter->filefilters, str);
239                 }
240                 else if (0 == sLine.compare(0, 2, _T("d:"), 2))
241                 {
242                         // directory filter
243                         String str = sLine.substr(2);
244                         AddFilterPattern(&pfilter->dirfilters, str);
245                 }
246         } while (bLinesLeft);
247
248         return pfilter;
249 }
250
251 /**
252  * @brief Give client back a pointer to the actual filter.
253  *
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
257  */
258 FileFilter * FileFilterMgr::GetFilterByPath(const String& szFilterPath)
259 {
260         vector<FileFilterPtr>::const_iterator iter = m_filters.begin();
261         while (iter != m_filters.end())
262         {
263                 if (strutils::compare_nocase((*iter)->fullpath, szFilterPath) == 0)
264                         return (*iter).get();
265                 ++iter;
266         }
267         return 0;
268 }
269
270 /**
271  * @brief Test given string against given regexp list.
272  *
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.
277  */
278 bool TestAgainstRegList(const vector<FileFilterElementPtr> *filterList, const String& szTest)
279 {
280         if (filterList->size() == 0)
281                 return false;
282
283         std::string compString;
284         ucr::toUTF8(szTest, compString);
285         vector<FileFilterElementPtr>::const_iterator iter = filterList->begin();
286         while (iter != filterList->end())
287         {
288                 RegularExpression::Match match;
289                 try
290                 {
291                         if ((*iter)->regexp.match(compString, 0, match) > 0)
292                                 return true;
293                 }
294                 catch (...)
295                 {
296                         // TODO:
297                 }
298                 
299                 ++iter;
300         }
301         return false;
302 }
303
304 /**
305  * @brief Test given filename against filefilter.
306  *
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.
310  *
311  * @param [in] pFilter Pointer to filefilter
312  * @param [in] szFileName Filename to test
313  * @return true if file passes the filter
314  */
315 bool FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
316         const String& szFileName) const
317 {
318         if (!pFilter)
319                 return true;
320         if (TestAgainstRegList(&pFilter->filefilters, szFileName))
321                 return !pFilter->default_include;
322         return pFilter->default_include;
323 }
324
325 /**
326  * @brief Test given directory name against filefilter.
327  *
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.
331  *
332  * @param [in] pFilter Pointer to filefilter
333  * @param [in] szDirName Directory name to test
334  * @return true if directory name passes the filter
335  */
336 bool FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
337         const String& szDirName) const
338 {
339         if (!pFilter)
340                 return true;
341         if (TestAgainstRegList(&pFilter->dirfilters, szDirName))
342                 return !pFilter->default_include;
343         return pFilter->default_include;
344 }
345
346 /**
347  * @brief Return name of filter.
348  *
349  * @param [in] i Index of filter.
350  * @return Name of filter in given index.
351  */
352 String FileFilterMgr::GetFilterName(int i) const
353 {
354         return m_filters[i]->name; 
355 }
356
357 /**
358  * @brief Return name of filter.
359  * @param [in] pFilter Filter to get name for.
360  * @return Given filter's name.
361  */
362 String FileFilterMgr::GetFilterName(const FileFilter *pFilter) const
363 {
364         return pFilter->name; 
365 }
366
367 /**
368  * @brief Return description of filter.
369  *
370  * @param [in] i Index of filter.
371  * @return Description of filter in given index.
372  */
373 String FileFilterMgr::GetFilterDesc(int i) const
374 {
375         return m_filters[i]->description; 
376 }
377
378 /**
379  * @brief Return description of filter.
380  * @param [in] pFilter Filter to get description for.
381  * @return Given filter's description.
382  */
383 String FileFilterMgr::GetFilterDesc(const FileFilter *pFilter) const
384 {
385         return pFilter->description;
386 }
387
388 /**
389  * @brief Return full path to filter.
390  *
391  * @param [in] i Index of filter.
392  * @return Full path of filter in given index.
393  */
394 String FileFilterMgr::GetFilterPath(int i) const
395 {
396         return m_filters[i]->fullpath;
397 }
398
399 /**
400  * @brief Return full path to filter.
401  *
402  * @param [in] pFilter Pointer to filter.
403  * @return Full path of filter.
404  */
405 String FileFilterMgr::GetFullpath(FileFilter * pfilter) const
406 {
407         return pfilter->fullpath;
408 }
409
410 /**
411  * @brief Reload filter from disk
412  *
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.
419  */
420 int FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
421 {
422         int errorcode = FILTER_OK;
423         FileFilter * newfilter = LoadFilterFile(pfilter->fullpath, errorcode);
424
425         if (newfilter == NULL)
426         {
427                 return errorcode;
428         }
429
430         vector<FileFilterPtr>::iterator iter = m_filters.begin();
431         while (iter != m_filters.end())
432         {
433                 if (pfilter == (*iter).get())
434                 {
435                         m_filters.erase(iter);
436                         break;
437                 }
438         }
439         m_filters.push_back(FileFilterPtr(newfilter));
440         return errorcode;
441 }
442
443 /**
444  * @brief Reload filter from disk.
445  *
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.
450  */
451 int FileFilterMgr::ReloadFilterFromDisk(const String& szFullPath)
452 {
453         int errorcode = FILTER_OK;
454         FileFilter * filter = GetFilterByPath(szFullPath);
455         if (filter)
456                 errorcode = ReloadFilterFromDisk(filter);
457         else
458                 errorcode = FILTER_NOTFOUND;
459         return errorcode;
460 }