OSDN Git Service

Merge from rev.7448
[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 // ID line follows -- this is updated by SVN
21 // $Id: FileFilterMgr.cpp 7024 2009-10-22 18:26:45Z kimmov $
22
23 #include <windows.h>
24 #include <string.h>
25 #include <vector>
26 #include <tchar.h>
27 #include "UnicodeString.h"
28 #include "FileFilter.h"
29 #include "pcre.h"
30 #include "FileFilterMgr.h"
31 #include "UniFile.h"
32 #include "coretools.h"
33 #include "Ucs2Utf8.h"
34 #include "paths.h"
35
36 using std::vector;
37
38 static void AddFilterPattern(vector<FileFilterElement*> *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(LPCTSTR szFilterFile)
54 {
55         int errorcode = FILTER_OK;
56         FileFilter * pFilter = LoadFilterFile(szFilterFile, errorcode);
57         if (pFilter)
58                 m_filters.push_back(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(LPCTSTR dir, LPCTSTR szPattern, LPCTSTR szExt)
69 {
70         const int extlen = szExt ? (int)_tcslen(szExt) : 0;
71         const String pattern = paths_ConcatPath(dir, szPattern);
72         WIN32_FIND_DATA ff;
73         HANDLE h = FindFirstFile(pattern.c_str(), &ff);
74         if (h != INVALID_HANDLE_VALUE)
75         {
76                 do
77                 {
78                         if (ff.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
79                                 continue;
80                         String filename = ff.cFileName;
81                         if (szExt)
82                         {
83                                 // caller specified a specific extension
84                                 // (This is really a workaround for brokenness in windows, which
85                                 //  doesn't screen correctly on extension in pattern)
86                                 String ext = filename.substr(filename.length() - extlen);
87                                 if (string_compare_nocase(szExt, ext) != 0)
88                                         return;
89                         }
90
91                         String filterpath = paths_ConcatPath(dir, ff.cFileName);
92                         AddFilter(filterpath.c_str());
93                 } while (FindNextFile(h, &ff));
94                 FindClose(h);
95         }
96 }
97
98 /**
99  * @brief Removes filter from filterlist.
100  *
101  * @param [in] szFilterFile Filename of filter to remove.
102  */
103 void FileFilterMgr::RemoveFilter(LPCTSTR szFilterFile)
104 {
105         // Note that m_filters.GetSize can change during loop
106         vector<FileFilter*>::iterator iter = m_filters.begin();
107         while (iter != m_filters.end())
108         {
109                 if (string_compare_nocase((*iter)->fullpath, szFilterFile) == 0)
110                 {
111                         delete (*iter);
112                         m_filters.erase(iter);
113                         break;
114                 }
115                 ++iter;
116         }
117 }
118
119 /**
120  * @brief Removes all filters from current list.
121  */
122 void FileFilterMgr::DeleteAllFilters()
123 {
124         while (!m_filters.empty())
125         {
126                 FileFilter* filter = m_filters.back();
127                 delete filter;
128                 m_filters.pop_back();
129         }
130 }
131
132 /**
133  * @brief Add a single pattern (if nonempty & valid) to a pattern list.
134  *
135  * @param [in] filterList List where pattern is added.
136  * @param [in] str Temporary variable (ie, it may be altered)
137  */
138 static void AddFilterPattern(vector<FileFilterElement*> *filterList, String & str)
139 {
140         LPCTSTR commentLeader = _T("##"); // Starts comment
141         str = string_trim_ws_begin(str);
142
143         // Ignore lines beginning with '##'
144         size_t pos = str.find(commentLeader);
145         if (pos == str.npos)
146                 return;
147
148         // Find possible comment-separator '<whitespace>##'
149         while (pos != std::string::npos && !_istspace(str[pos - 1]))
150                 pos = str.find(commentLeader, pos + 1);
151
152         // Remove comment and whitespaces before it
153         if (pos != std::string::npos)
154                 str = str.substr(0, pos);
155         str = string_trim_ws_end(str);
156         if (str.empty())
157                 return;
158
159         const char * errormsg = NULL;
160         int erroroffset = 0;
161
162         char *regexString = UCS2UTF8_ConvertToUtf8(str.c_str());
163         int pcre_opts = 0;
164
165 #ifdef UNICODE
166         pcre_opts |= PCRE_UTF8;
167 #endif
168         pcre_opts |= PCRE_CASELESS;
169         pcre *regexp = pcre_compile(regexString, pcre_opts, &errormsg,
170                 &erroroffset, NULL);
171         if (regexp)
172         {
173                 FileFilterElement *elem = new FileFilterElement();
174                 errormsg = NULL;
175
176                 pcre_extra *pe = pcre_study(regexp, 0, &errormsg);
177                 elem->pRegExp = regexp;
178                 
179                 if (pe != NULL && errormsg != NULL)
180                         elem->pRegExpExtra = pe;
181                 
182                 filterList->push_back(elem);
183         }
184         UCS2UTF8_Dealloc(regexString);
185 }
186
187 /**
188  * @brief Parse a filter file, and add it to array if valid.
189  *
190  * @param [in] szFilePath Path (w/ filename) to file to load.
191  * @param [out] error Error-code if loading failed (returned NULL).
192  * @return Pointer to new filter, or NULL if error (check error code too).
193  */
194 FileFilter * FileFilterMgr::LoadFilterFile(LPCTSTR szFilepath, int & error)
195 {
196         UniMemFile file;
197         if (!file.OpenReadOnly(szFilepath))
198         {
199                 error = FILTER_ERROR_FILEACCESS;
200                 return NULL;
201         }
202
203         file.ReadBom(); // in case it is a Unicode file, let UniMemFile handle BOM
204
205         String fileName;
206         SplitFilename(szFilepath, NULL, &fileName, NULL);
207         FileFilter *pfilter = new FileFilter;
208         pfilter->fullpath = szFilepath;
209         pfilter->name = fileName.c_str(); // Filename is the default name
210
211         String sLine;
212         bool lossy = false;
213         bool bLinesLeft = true;
214         do
215         {
216                 // Returns false when last line is read
217                 String tmpLine;
218                 bLinesLeft = file.ReadString(tmpLine, &lossy);
219                 sLine = tmpLine;
220                 sLine = string_trim_ws(sLine);
221
222                 if (0 == _tcsncmp(sLine.c_str(), _T("name:"), 5))
223                 {
224                         // specifies display name
225                         String str = sLine.substr(5);
226                         str = string_trim_ws_begin(str);
227                         if (!str.empty())
228                                 pfilter->name = str;
229                 }
230                 else if (0 == _tcsncmp(sLine.c_str(), _T("desc:"), 5))
231                 {
232                         // specifies display name
233                         String str = sLine.substr(5);
234                         str = string_trim_ws_begin(str);
235                         if (!str.empty())
236                                 pfilter->description = str;
237                 }
238                 else if (0 == _tcsncmp(sLine.c_str(), _T("def:"), 4))
239                 {
240                         // specifies default
241                         String str = sLine.substr(4);
242                         str = string_trim_ws_begin(str);
243                         if (str == _T("0") || str == _T("no") || str == _T("exclude"))
244                                 pfilter->default_include = false;
245                         else if (str == _T("1") || str == _T("yes") || str == _T("include"))
246                                 pfilter->default_include = true;
247                 }
248                 else if (0 == _tcsncmp(sLine.c_str(), _T("f:"), 2))
249                 {
250                         // file filter
251                         String str = sLine.substr(2);
252                         AddFilterPattern(&pfilter->filefilters, str);
253                 }
254                 else if (0 == _tcsncmp(sLine.c_str(), _T("d:"), 2))
255                 {
256                         // directory filter
257                         String str = sLine.substr(2);
258                         AddFilterPattern(&pfilter->dirfilters, str);
259                 }
260         } while (bLinesLeft);
261
262         return pfilter;
263 }
264
265 /**
266  * @brief Give client back a pointer to the actual filter.
267  *
268  * @param [in] szFilterPath Full path to filterfile.
269  * @return Pointer to found filefilter or NULL;
270  * @note We just do a linear search, because this is seldom called
271  */
272 FileFilter * FileFilterMgr::GetFilterByPath(LPCTSTR szFilterPath)
273 {
274         vector<FileFilter*>::const_iterator iter = m_filters.begin();
275         while (iter != m_filters.end())
276         {
277                 if (string_compare_nocase((*iter)->fullpath, szFilterPath) == 0)
278                         return (*iter);
279                 ++iter;
280         }
281         return 0;
282 }
283
284 /**
285  * @brief Test given string against given regexp list.
286  *
287  * @param [in] filterList List of regexps to test against.
288  * @param [in] szTest String to test against regexps.
289  * @return TRUE if string passes
290  * @note Matching stops when first match is found.
291  */
292 BOOL TestAgainstRegList(const vector<FileFilterElement*> *filterList, LPCTSTR szTest)
293 {
294         int ovector[30];
295         char *compString = UCS2UTF8_ConvertToUtf8(szTest);
296         size_t stringlen = strlen(compString);
297         int result = 0;
298         vector<FileFilterElement*>::const_iterator iter = filterList->begin();
299         while (iter != filterList->end())
300         {
301                 pcre * regexp = (*iter)->pRegExp;
302                 pcre_extra * extra = (*iter)->pRegExpExtra;
303                 result = pcre_exec(regexp, extra, compString,(int) stringlen,
304                         0, 0, ovector, 30);
305                 if (result >= 0)
306                         break;
307                 ++iter;
308         }
309         UCS2UTF8_Dealloc(compString);
310         if (result >= 0)
311                 return TRUE;
312         return FALSE;
313 }
314
315 /**
316  * @brief Test given filename against filefilter.
317  *
318  * Test filename against active filefilter. If matching rule is found
319  * we must first determine type of rule that matched. If we return FALSE
320  * from this function directory scan marks file as skipped.
321  *
322  * @param [in] pFilter Pointer to filefilter
323  * @param [in] szFileName Filename to test
324  * @return TRUE if file passes the filter
325  */
326 BOOL FileFilterMgr::TestFileNameAgainstFilter(const FileFilter * pFilter,
327         LPCTSTR szFileName) const
328 {
329         if (!pFilter)
330                 return TRUE;
331         if (TestAgainstRegList(&pFilter->filefilters, szFileName))
332                 return !pFilter->default_include;
333         return pFilter->default_include;
334 }
335
336 /**
337  * @brief Test given directory name against filefilter.
338  *
339  * Test directory name against active filefilter. If matching rule is found
340  * we must first determine type of rule that matched. If we return FALSE
341  * from this function directory scan marks file as skipped.
342  *
343  * @param [in] pFilter Pointer to filefilter
344  * @param [in] szDirName Directory name to test
345  * @return TRUE if directory name passes the filter
346  */
347 BOOL FileFilterMgr::TestDirNameAgainstFilter(const FileFilter * pFilter,
348         LPCTSTR szDirName) const
349 {
350         if (!pFilter)
351                 return TRUE;
352         if (TestAgainstRegList(&pFilter->dirfilters, szDirName))
353                 return !pFilter->default_include;
354         return pFilter->default_include;
355 }
356
357 /**
358  * @brief Return name of filter.
359  *
360  * @param [in] i Index of filter.
361  * @return Name of filter in given index.
362  */
363 String FileFilterMgr::GetFilterName(int i) const
364 {
365         return m_filters[i]->name; 
366 }
367
368 /**
369  * @brief Return name of filter.
370  * @param [in] pFilter Filter to get name for.
371  * @return Given filter's name.
372  */
373 String FileFilterMgr::GetFilterName(const FileFilter *pFilter) const
374 {
375         return pFilter->name; 
376 }
377
378 /**
379  * @brief Return description of filter.
380  *
381  * @param [in] i Index of filter.
382  * @return Description of filter in given index.
383  */
384 String FileFilterMgr::GetFilterDesc(int i) const
385 {
386         return m_filters[i]->description; 
387 }
388
389 /**
390  * @brief Return description of filter.
391  * @param [in] pFilter Filter to get description for.
392  * @return Given filter's description.
393  */
394 String FileFilterMgr::GetFilterDesc(const FileFilter *pFilter) const
395 {
396         return pFilter->description;
397 }
398
399 /**
400  * @brief Return full path to filter.
401  *
402  * @param [in] i Index of filter.
403  * @return Full path of filter in given index.
404  */
405 String FileFilterMgr::GetFilterPath(int i) const
406 {
407         return m_filters[i]->fullpath;
408 }
409
410 /**
411  * @brief Return full path to filter.
412  *
413  * @param [in] pFilter Pointer to filter.
414  * @return Full path of filter.
415  */
416 String FileFilterMgr::GetFullpath(FileFilter * pfilter) const
417 {
418         return pfilter->fullpath;
419 }
420
421 /**
422  * @brief Reload filter from disk
423  *
424  * Reloads filter from disk. This is done by creating a new one
425  * to substitute for old one.
426  * @param [in] pFilter Pointer to filter to reload.
427  * @return FILTER_OK when succeeds, one of FILTER_RETVALUE values on error.
428  * @note Given filter (pfilter) is freed and must not be used anymore.
429  * @todo Should return new filter.
430  */
431 int FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
432 {
433         int errorcode = FILTER_OK;
434         FileFilter * newfilter = LoadFilterFile(pfilter->fullpath.c_str(), errorcode);
435
436         if (newfilter == NULL)
437         {
438                 return errorcode;
439         }
440
441         vector<FileFilter*>::iterator iter = m_filters.begin();
442         while (iter != m_filters.end())
443         {
444                 if (pfilter == (*iter))
445                 {
446                         delete (*iter);
447                         m_filters.erase(iter);
448                         break;
449                 }
450         }
451         m_filters.push_back(newfilter);
452         return errorcode;
453 }
454
455 /**
456  * @brief Reload filter from disk.
457  *
458  * Reloads filter from disk. This is done by creating a new one
459  * to substitute for old one.
460  * @param [in] szFullPath Full path to filter file to reload.
461  * @return FILTER_OK when succeeds or one of FILTER_RETVALUE values when fails.
462  */
463 int FileFilterMgr::ReloadFilterFromDisk(LPCTSTR szFullPath)
464 {
465         int errorcode = FILTER_OK;
466         FileFilter * filter = GetFilterByPath(szFullPath);
467         if (filter)
468                 errorcode = ReloadFilterFromDisk(filter);
469         else
470                 errorcode = FILTER_NOTFOUND;
471         return errorcode;
472 }