OSDN Git Service

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