OSDN Git Service

Fix issue #2046: Folder compare omits unique folders from results if they contain...
[winmerge-jp/winmerge-jp.git] / Src / DiffContext.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /**
8  *  @file DiffContext.cpp
9  *
10  *  @brief Implementation of CDiffContext
11  */ 
12
13 #include "pch.h"
14 #include "DiffContext.h"
15 #include <Poco/ScopedLock.h>
16 #include "CompareOptions.h"
17 #include "VersionInfo.h"
18 #include "paths.h"
19 #include "codepage_detect.h"
20 #include "DiffItemList.h"
21 #include "IAbortable.h"
22 #include "DiffWrapper.h"
23 #include "DebugNew.h"
24
25 using Poco::FastMutex;
26
27 //////////////////////////////////////////////////////////////////////
28 // Construction/Destruction
29 //////////////////////////////////////////////////////////////////////
30
31 /**
32  * @brief Construct CDiffContext.
33  *
34  * @param [in] pszLeft Initial left-side path.
35  * @param [in] pszRight Initial right-side path.
36  * @param [in] compareMethod Main compare method for this compare.
37  */
38 CDiffContext::CDiffContext(const PathContext & paths, int compareMethod)
39 : m_piFilterGlobal(nullptr)
40 , m_piPluginInfos(nullptr)
41 , m_nCompMethod(compareMethod)
42 , m_bIgnoreSmallTimeDiff(false)
43 , m_pCompareStats(nullptr)
44 , m_piAbortable(nullptr)
45 , m_bStopAfterFirstDiff(false)
46 , m_pFilterList(nullptr)
47 , m_pSubstitutionList(nullptr)
48 , m_pContentCompareOptions(nullptr)
49 , m_pQuickCompareOptions(nullptr)
50 , m_pOptions(nullptr)
51 , m_bPluginsEnabled(false)
52 , m_bRecursive(false)
53 , m_bWalkUniques(true)
54 , m_bIgnoreReparsePoints(false)
55 , m_bIgnoreCodepage(false)
56 , m_iGuessEncodingType(0)
57 , m_nQuickCompareLimit(0)
58 , m_nBinaryCompareLimit(0)
59 , m_bEnableImageCompare(false)
60 , m_pImgfileFilter(nullptr)
61 , m_dColorDistanceThreshold(0.0)
62 {
63         int index;
64         for (index = 0; index < paths.GetSize(); index++)
65                 m_paths.SetPath(index, paths[index]);
66 }
67
68 /**
69  * @brief Destructor.
70  */
71 CDiffContext::~CDiffContext() = default;
72
73 /**
74  * @brief Update info in item in result list from disk.
75  * This function updates result list item's file information from actual
76  * file in the disk. This updates info like date, size and attributes.
77  * @param [in] diffpos DIFFITEM to update.
78  * @param [in] nIndex index to update 
79  */
80 void CDiffContext::UpdateStatusFromDisk(DIFFITEM *diffpos, int nIndex)
81 {
82         DIFFITEM &di = GetDiffRefAt(diffpos);
83         di.diffFileInfo[nIndex].ClearPartial();
84         if (di.diffcode.exists(nIndex))
85                 UpdateInfoFromDiskHalf(di, nIndex);
86 }
87
88 /**
89  * @brief Update file information from disk for DIFFITEM.
90  * This function updates DIFFITEM's file information from actual file in
91  * the disk. This updates info like date, size and attributes.
92  * @param [in, out] di DIFFITEM to update.
93  * @param [in] nIndex index to update
94  * @return true if file exists
95  */
96 bool CDiffContext::UpdateInfoFromDiskHalf(DIFFITEM &di, int nIndex)
97 {
98         String filepath = paths::ConcatPath(paths::ConcatPath(m_paths[nIndex], di.diffFileInfo[nIndex].path), di.diffFileInfo[nIndex].filename);
99         DiffFileInfo & dfi = di.diffFileInfo[nIndex];
100         if (!dfi.Update(filepath))
101                 return false;
102         UpdateVersion(di, nIndex);
103         dfi.encoding = codepage_detect::Guess(filepath, m_iGuessEncodingType);
104         return true;
105 }
106
107 /**
108  * @brief Determine if file is one to have a version information.
109  * This function determines if the given file has a version information
110  * attached into it in resource. This is done by comparing file extension to
111  * list of known filename extensions usually to have a version information.
112  * @param [in] ext Extension to check.
113  * @return true if extension has version info, false otherwise.
114  */
115 static bool CheckFileForVersion(const String& ext)
116 {
117         String lower_ext = strutils::makelower(ext);
118         if (lower_ext == _T(".exe") || lower_ext == _T(".dll") || lower_ext == _T(".sys") ||
119             lower_ext == _T(".drv") || lower_ext == _T(".ocx") || lower_ext == _T(".cpl") ||
120             lower_ext == _T(".scr") || lower_ext == _T(".lang"))
121         {
122                 return true;
123         }
124         return false;
125 }
126
127 /**
128  * @brief Load file version from disk.
129  * Update fileversion for given item and side from disk. Note that versions
130  * are read from only some filetypes. See CheckFileForVersion() function
131  * for list of files to check versions.
132  * @param [in,out] di DIFFITEM to update.
133  * @param [in] bLeft If true left-side file is updated, right-side otherwise.
134  */
135 void CDiffContext::UpdateVersion(DIFFITEM &di, int nIndex) const
136 {
137         DiffFileInfo & dfi = di.diffFileInfo[nIndex];
138         // Check only binary files
139         dfi.version.SetFileVersionNone();
140
141         if (di.diffcode.isDirectory())
142                 return;
143         
144         String spath;
145         if (!di.diffcode.exists(nIndex))
146                 return;
147         String ext = paths::FindExtension(di.diffFileInfo[nIndex].filename);
148         if (!CheckFileForVersion(ext))
149                 return;
150         spath = di.getFilepath(nIndex, GetNormalizedPath(nIndex));
151         spath = paths::ConcatPath(spath, di.diffFileInfo[nIndex].filename);
152         
153         // Get version info if it exists
154         CVersionInfo ver(spath.c_str());
155         unsigned verMS = 0;
156         unsigned verLS = 0;
157         if (ver.GetFixedFileVersion(verMS, verLS))
158                 dfi.version.SetFileVersion(verMS, verLS);
159 }
160
161 /**
162  * @brief Create compare-method specific compare options class.
163  * This function creates a compare options class that is specific for
164  * main compare method. Compare options class is initialized from
165  * given set of options.
166  * @param [in] compareMethod Selected compare method.
167  * @param [in] options Initial set of compare options.
168  * @return true if creation succeeds.
169  */
170 bool CDiffContext::CreateCompareOptions(int compareMethod, const DIFFOPTIONS & options)
171 {
172         m_pContentCompareOptions.reset();
173         m_pQuickCompareOptions.reset();
174         m_pOptions.reset(new DIFFOPTIONS);
175         *m_pOptions.get() = options;
176
177         m_nCompMethod = compareMethod;
178         if (GetCompareOptions(m_nCompMethod) == nullptr)
179         {
180                 // For Date and Date+Size compare `nullptr` is ok since they don't have actual
181                 // compare options.
182                 if (m_nCompMethod == CMP_DATE || m_nCompMethod == CMP_DATE_SIZE ||
183                         m_nCompMethod == CMP_SIZE)
184                 {
185                         return true;
186                 }
187                 else
188                         return false;
189         }
190         return true;
191 }
192
193 /**
194  * @brief Get compare-type specific compare options.
195  * This function returns per-compare method options. The compare options
196  * returned are converted from general options to match options for specific
197  * comapare type. Not all compare options in general set are available for
198  * some other compare type. And some options can have different values.
199  * @param [in] compareMethod Compare method used.
200  * @return Compare options class.
201  */
202 CompareOptions * CDiffContext::GetCompareOptions(int compareMethod)
203 {
204         FastMutex::ScopedLock lock(m_mutex);
205         CompareOptions *pCompareOptions = nullptr;
206
207         // Otherwise we have to create new options
208         switch (compareMethod)
209         {
210         case CMP_CONTENT:
211                 if (m_pContentCompareOptions != nullptr)
212                         return m_pContentCompareOptions.get();
213                 m_pContentCompareOptions.reset(pCompareOptions = new DiffutilsOptions());
214                 break;
215
216         case CMP_QUICK_CONTENT:
217                 if (m_pQuickCompareOptions != nullptr)
218                         return m_pQuickCompareOptions.get();
219                 m_pQuickCompareOptions.reset(pCompareOptions = new QuickCompareOptions());
220                 break;
221         }
222
223
224         if (pCompareOptions != nullptr)
225                 pCompareOptions->SetFromDiffOptions(*m_pOptions);
226
227         return pCompareOptions;
228 }
229
230 /** @brief Forward call to retrieve plugin info (winds up in DirDoc) */
231 void CDiffContext::FetchPluginInfos(const String& filteredFilenames,
232                 PackingInfo ** infoUnpacker, PrediffingInfo ** infoPrediffer)
233 {
234         if (!m_piPluginInfos)
235                 return;
236         m_piPluginInfos->FetchPluginInfos(filteredFilenames, infoUnpacker, infoPrediffer);
237 }
238
239 /**
240  * @brief Check if user has requested aborting the compare.
241  * @return true if user has requested abort, false otherwise.
242  */
243 bool CDiffContext::ShouldAbort() const
244 {
245         return m_piAbortable!=nullptr && m_piAbortable->ShouldAbort();
246 }
247
248 /**
249  * @brief Get actual compared paths from DIFFITEM.
250  * @param [in] pCtx Pointer to compare context.
251  * @param [in] di DiffItem from which the paths are created.
252  * @param [out] left Gets the left compare path.
253  * @param [out] right Gets the right compare path.
254  * @note If item is unique, same path is returned for both.
255  */
256 void CDiffContext::GetComparePaths(const DIFFITEM &di, PathContext & tFiles) const
257 {
258         int nDirs = GetCompareDirs();
259
260         tFiles.SetSize(nDirs);
261
262         for (int nIndex = 0; nIndex < nDirs; nIndex++)
263         {
264                 if (di.diffcode.exists(nIndex))
265                 {
266                         tFiles.SetPath(nIndex,
267                                 paths::ConcatPath(GetPath(nIndex), di.diffFileInfo[nIndex].GetFile()), false);
268                 }
269                 else
270                 {
271                         tFiles.SetPath(nIndex, _T("NUL"), false);
272                 }
273         }
274 }
275
276 String CDiffContext::GetFilteredFilenames(const DIFFITEM& di) const
277 {
278         PathContext paths;
279         GetComparePaths(di, paths);
280         return GetFilteredFilenames(paths);
281 }
282
283 void CDiffContext::CreateDuplicateValueMap()
284 {
285         if (!m_pPropertySystem)
286                 return;
287         const int nDirs = GetCompareDirs();
288         m_duplicateValues.clear();
289         m_duplicateValues.resize(m_pPropertySystem->GetCanonicalNames().size());
290         DIFFITEM *pos = GetFirstDiffPosition();
291         std::vector<int> currentGroupId(m_duplicateValues.size());
292         while (pos != nullptr)
293         {
294                 const DIFFITEM& di = GetNextDiffPosition(pos);
295                 for (int pane = 0; pane < nDirs; ++pane)
296                 {
297                         const PropertyValues* pValues = di.diffFileInfo[pane].m_pAdditionalProperties.get();
298                         if (pValues)
299                         {
300                                 for (size_t j = 0; j < pValues->GetSize(); ++j)
301                                 {
302                                         if (pValues->IsHashValue(j) )
303                                         {
304                                                 std::vector<uint8_t> value = pValues->GetHashValue(j);
305                                                 if (!value.empty())
306                                                 {
307                                                         auto it = m_duplicateValues[j].find(value);
308                                                         if (it == m_duplicateValues[j].end())
309                                                         {
310                                                                 DuplicateInfo info{};
311                                                                 ++info.count[pane];
312                                                                 info.nonpaired = !di.diffcode.existAll();
313                                                                 m_duplicateValues[j].insert_or_assign(value, info);
314                                                         }
315                                                         else
316                                                         {
317                                                                 if (it->second.groupid == 0)
318                                                                 {
319                                                                         int count = 0;
320                                                                         for (int k = 0; k < nDirs; ++k)
321                                                                                 count += it->second.count[k];
322                                                                         if (count > 0)
323                                                                         {
324                                                                                 ++currentGroupId[j];
325                                                                                 it->second.groupid = currentGroupId[j];
326                                                                         }
327                                                                 }
328                                                                 ++it->second.count[pane];
329                                                                 if (!it->second.nonpaired && !di.diffcode.existAll())
330                                                                         it->second.nonpaired = true;
331                                                         }
332                                                 }
333                                         }
334                                 }
335                         }
336                 }
337         }
338 }