OSDN Git Service

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