OSDN Git Service

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