OSDN Git Service

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