OSDN Git Service

PATCH: [ 1114360 ] Non-blocking file filter editing
[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
22 #ifdef _DEBUG
23 #define new DEBUG_NEW
24 #undef THIS_FILE
25 static char THIS_FILE[] = __FILE__;
26 #endif
27
28 void DeleteRegList(RegList & reglist)
29 {
30         while (!reglist.IsEmpty())
31         {
32                 CRegExp * regexp = reglist.RemoveHead();
33                 delete regexp;
34         }
35 }
36
37 /**
38  * @brief One actual filter.
39  *
40  * For example, this might be a GNU C filter, excluding *.o files and CVS
41  * directories. That is to say, a filter is a set of file masks and
42  * directory masks
43  */
44 struct FileFilter
45 {
46         bool default_include;
47         CString name;
48         CString description;
49         CString fullpath;
50         RegList filefilters; 
51         RegList dirfilters; 
52         FileFilter() : default_include(true) { }
53         ~FileFilter();
54 };
55 FileFilter::~FileFilter()
56 {
57         DeleteRegList(filefilters);
58         DeleteRegList(dirfilters);
59 }
60
61 FileFilterMgr::~FileFilterMgr()
62 {
63         DeleteAllFilters();
64 }
65
66 /**
67  * @brief Load filter file from disk.
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         // DeleteAllFilters();
75         CFileFind finder;
76         BOOL bWorking = finder.FindFile(szPattern);
77         int extlen = szExt ? _tcslen(szExt) : 0;
78         while (bWorking)
79         {
80                 bWorking = finder.FindNextFile();
81                 if (finder.IsDots() || finder.IsDirectory())
82                         continue;
83                 CString sFilename = finder.GetFileName();
84                 if (szExt)
85                 {
86                         // caller specified a specific extension
87                         // (This is really a workaround for brokenness in windows, which
88                         //  doesn't screen correctly on extension in pattern)
89                         if (sFilename.Right(extlen).CompareNoCase(szExt))
90                                 return;
91                 }
92                 FileFilter * pfilter = LoadFilterFile(finder.GetFilePath(), sFilename);
93                 m_filters.Add(pfilter);
94         }
95 }
96
97 /**
98  * @brief Removes all filters from current list.
99  */
100 void FileFilterMgr::DeleteAllFilters()
101 {
102         for (int i=0; i<m_filters.GetSize(); ++i)
103         {
104                 delete m_filters[i];
105                 m_filters[i] = 0;
106         }
107         m_filters.RemoveAll();
108 }
109
110 /**
111  * @brief Add a single pattern (if nonempty & valid) to a pattern list.
112  *
113  * @param [in] RegList List where pattern is added.
114  * @param [in] str Temporary variable (ie, it may be altered)
115  */
116 static void AddFilterPattern(RegList & reglist, CString & str)
117 {
118         LPCTSTR commentLeader = _T("##"); // Starts comment
119         str.MakeUpper();
120         str.TrimLeft();
121
122         // Ignore lines beginning with '##'
123         int pos = str.Find(commentLeader);
124         if (pos == 0)
125                 return;
126
127         // Find possible comment-separator '<whitespace>##'
128         while (pos > 0 && !_istspace(str[pos - 1]))
129                 pos = str.Find(commentLeader, pos);     
130
131         // Remove comment and whitespaces before it
132         if (pos > 0)
133                 str = str.Left(pos);
134         str.TrimRight();
135         if (str.IsEmpty())
136                 return;
137
138         CRegExp * regexp = new CRegExp;
139         if (regexp->RegComp(str))
140                 reglist.AddTail(regexp);
141         else
142                 delete regexp;
143 }
144
145 /**
146  * @brief Parse a filter file, and add it to array if valid.
147  *
148  * @param [in] szFilePath Path (w/o filename) to file to load.
149  * @param [in] szFilename Name of file to load.
150  */
151 FileFilter * FileFilterMgr::LoadFilterFile(LPCTSTR szFilepath, LPCTSTR szFilename)
152 {
153         CStdioFile file;
154         if (!file.Open(szFilepath, CFile::modeRead | CFile::shareDenyNone))
155                 return NULL;
156         FileFilter *pfilter = new FileFilter;
157         pfilter->fullpath = szFilepath;
158         pfilter->name = szFilename; // default if no name
159         CString sLine;
160         while (file.ReadString(sLine))
161         {
162                 sLine.TrimLeft();
163                 sLine.TrimRight();
164
165                 if (0 == _tcsncmp(sLine, _T("name:"), 5))
166                 {
167                         // specifies display name
168                         CString str = sLine.Mid(5);
169                         str.TrimLeft();
170                         if (!str.IsEmpty())
171                                 pfilter->name = str;
172                 }
173                 else if (0 == _tcsncmp(sLine, _T("desc:"), 5))
174                 {
175                         // specifies display name
176                         CString str = sLine.Mid(5);
177                         str.TrimLeft();
178                         if (!str.IsEmpty())
179                                 pfilter->description = str;
180                 }
181                 else if (0 == _tcsncmp(sLine, _T("def:"), 4))
182                 {
183                         // specifies default
184                         CString str = sLine.Mid(4);
185                         str.TrimLeft();
186                         if (str == _T("0") || str == _T("no") || str == _T("exclude"))
187                                 pfilter->default_include = false;
188                         else if (str == _T("1") || str == _T("yes") || str == _T("include"))
189                                 pfilter->default_include = true;
190                 }
191                 else if (0 == _tcsncmp(sLine, _T("f:"), 2))
192                 {
193                         // file filter
194                         CString str = sLine.Mid(2);
195                         AddFilterPattern(pfilter->filefilters, str);
196                 }
197                 else if (0 == _tcsncmp(sLine, _T("d:"), 2))
198                 {
199                         // directory filter
200                         CString str = sLine.Mid(2);
201                         AddFilterPattern(pfilter->dirfilters, str);
202                 }
203         }
204         return pfilter;
205 }
206
207 /**
208  * @brief Give client back a pointer to the actual filter.
209  *
210  * @param [in] szFilterPath Full path to filterfile.
211  * @return Pointer to found filefilter or NULL;
212  * @note We just do a linear search, because this is seldom called
213  */
214 FileFilter * FileFilterMgr::GetFilterByPath(LPCTSTR szFilterPath)
215 {
216         for (int i=0; i<m_filters.GetSize(); ++i)
217         {
218                 if (m_filters[i]->fullpath == szFilterPath)
219                         return m_filters[i];
220         }
221         return 0;
222 }
223
224 /**
225  * @brief Test given string against given regexp list.
226  *
227  * @param [in] reglist List of regexps to test against.
228  * @param [in] szTest String to test against regexps.
229  * @return TRUE if string passes
230  */
231 BOOL TestAgainstRegList(const RegList & reglist, LPCTSTR szTest)
232 {
233         CString str = szTest;
234         str.MakeUpper();
235         for (POSITION pos = reglist.GetHeadPosition(); pos; )
236         {
237                 CRegExp * regexp = reglist.GetNext(pos);
238                 if (regexp->RegFind(str) != -1)
239                         return TRUE;
240         }
241         return FALSE;
242 }
243
244 /**
245  * @brief Test given filename against filefilter.
246  *
247  * Test filename against active filefilter. If matching rule is found
248  * we must first determine type of rule that matched. If we return FALSE
249  * from this function directory scan marks file as skipped.
250  *
251  * @param [in] pFilter Pointer to filefilter
252  * @param [in] szFileName Filename to test
253  * @return TRUE if file passes the filter
254  */
255 BOOL FileFilterMgr::TestFileNameAgainstFilter(FileFilter * pFilter, LPCTSTR szFileName)
256 {
257         if (!pFilter) return TRUE;
258         if (TestAgainstRegList(pFilter->filefilters, szFileName))
259                 return !pFilter->default_include;
260         return pFilter->default_include;
261 }
262
263 /**
264  * @brief Test given directory name against filefilter.
265  *
266  * Test directory name against active filefilter. If matching rule is found
267  * we must first determine type of rule that matched. If we return FALSE
268  * from this function directory scan marks file as skipped.
269  *
270  * @param [in] pFilter Pointer to filefilter
271  * @param [in] szDirName Directory name to test
272  * @return TRUE if directory name passes the filter
273  */
274 BOOL FileFilterMgr::TestDirNameAgainstFilter(FileFilter * pFilter, LPCTSTR szDirName)
275 {
276         if (!pFilter) return TRUE;
277         if (TestAgainstRegList(pFilter->dirfilters, szDirName))
278                 return !pFilter->default_include;
279         return pFilter->default_include;
280 }
281
282 /**
283  * @brief Return name of filter.
284  *
285  * @param [in] i Index of filter.
286  * @return Name of filter in given index.
287  */
288 CString FileFilterMgr::GetFilterName(int i) const
289 {
290         return m_filters[i]->name; 
291 }
292
293 /**
294  * @brief Return description of filter.
295  *
296  * @param [in] i Index of filter.
297  * @return Description of filter in given index.
298  */
299 CString FileFilterMgr::GetFilterDesc(int i) const
300 {
301         return m_filters[i]->description; 
302 }
303
304 /**
305  * @brief Return full path to filter.
306  *
307  * @param [in] i Index of filter.
308  * @return Full path of filter in given index.
309  */
310 CString FileFilterMgr::GetFilterPath(int i) const
311 {
312         return m_filters[i]->fullpath;
313 }
314
315 /**
316  * @brief Return full path to filter.
317  *
318  * @param [in] pFilter Pointer to filter.
319  * @return Full path of filter.
320  */
321 CString FileFilterMgr::GetFullpath(FileFilter * pfilter) const
322 {
323         return pfilter->fullpath;
324 }
325
326 /**
327  * @brief Reload filter from disk
328  *
329  * Reloads filter from disk. This is done by creating a new one
330  * to substitute for old one.
331  * @param [in] pFilter Pointer to filter to reload.
332  */
333 void FileFilterMgr::ReloadFilterFromDisk(FileFilter * pfilter)
334 {
335         FileFilter * newfilter = LoadFilterFile(pfilter->fullpath, pfilter->name);
336         for (int i=0; i<m_filters.GetSize(); ++i)
337         {
338                 if (pfilter == m_filters[i])
339                 {
340                         m_filters.RemoveAt(i);
341                         delete pfilter;
342                         break;
343                 }
344         }
345         m_filters.Add(newfilter);
346 }
347
348 /**
349  * @brief Reload filter from disk
350  *
351  * Reloads filter from disk. This is done by creating a new one
352  * to substitute for old one.
353  * @param [in] szFullPath Full path to filter file to reload.
354  */
355 void FileFilterMgr::ReloadFilterFromDisk(LPCTSTR szFullPath)
356 {
357         FileFilter * filter = GetFilterByPath(szFullPath);
358         ReloadFilterFromDisk(filter);
359 }