OSDN Git Service

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