4 * @brief Implementation file for FolderCmp
11 #include "Wrap_DiffUtils.h"
12 #include "ByteCompare.h"
14 #include "FilterList.h"
15 #include "DiffContext.h"
17 #include "DiffWrapper.h"
18 #include "FileTransform.h"
19 #include "codepage_detect.h"
20 #include "BinaryCompare.h"
21 #include "TimeSizeCompare.h"
23 #include "FileFilterHelper.h"
27 using CompareEngines::ByteCompare;
28 using CompareEngines::BinaryCompare;
29 using CompareEngines::TimeSizeCompare;
30 using CompareEngines::ImageCompare;
32 FolderCmp::FolderCmp(CDiffContext *pCtxt)
34 , m_pDiffUtilsEngine(nullptr)
35 , m_pByteCompare(nullptr)
36 , m_pBinaryCompare(nullptr)
37 , m_pTimeSizeCompare(nullptr)
38 , m_ndiffs(CDiffContext::DIFFS_UNKNOWN)
39 , m_ntrivialdiffs(CDiffContext::DIFFS_UNKNOWN)
43 FolderCmp::~FolderCmp()
47 bool FolderCmp::RunPlugins(PluginsContext * plugCtxt, String &errStr)
53 void FolderCmp::CleanupAfterPlugins(PluginsContext *plugCtxt)
58 * @brief Prepare files (run plugins) & compare them, and return diffcode.
59 * This is function to compare two files in folder compare. It is not used in
61 * @param [in] pCtxt Pointer to compare context.
62 * @param [in, out] di Compared files with associated data.
63 * @return Compare result code.
65 int FolderCmp::prepAndCompareFiles(DIFFITEM &di)
68 int nCompMethod = m_pCtxt->GetCompareMethod();
69 int nDirs = m_pCtxt->GetCompareDirs();
71 unsigned code = DIFFCODE::FILE | DIFFCODE::CMPERR;
73 if (nCompMethod == CMP_CONTENT || nCompMethod == CMP_QUICK_CONTENT)
75 if ((di.diffFileInfo[0].size > m_pCtxt->m_nBinaryCompareLimit && di.diffFileInfo[0].size != DirItem::FILE_SIZE_NONE) ||
76 (di.diffFileInfo[1].size > m_pCtxt->m_nBinaryCompareLimit && di.diffFileInfo[1].size != DirItem::FILE_SIZE_NONE) ||
77 (nDirs > 2 && di.diffFileInfo[2].size > m_pCtxt->m_nBinaryCompareLimit && di.diffFileInfo[2].size != DirItem::FILE_SIZE_NONE))
79 nCompMethod = CMP_BINARY_CONTENT;
81 else if (m_pCtxt->m_bEnableImageCompare && (
82 di.diffFileInfo[0].size != DirItem::FILE_SIZE_NONE && m_pCtxt->m_pImgfileFilter->includeFile(di.diffFileInfo[0].filename) ||
83 di.diffFileInfo[1].size != DirItem::FILE_SIZE_NONE && m_pCtxt->m_pImgfileFilter->includeFile(di.diffFileInfo[1].filename) ||
84 nDirs > 2 && di.diffFileInfo[2].size != DirItem::FILE_SIZE_NONE && m_pCtxt->m_pImgfileFilter->includeFile(di.diffFileInfo[2].filename)))
86 nCompMethod = CMP_IMAGE_CONTENT;
90 if (nCompMethod == CMP_CONTENT ||
91 nCompMethod == CMP_QUICK_CONTENT)
95 for (nIndex = 0; nIndex < nDirs; nIndex++)
96 m_diffFileData.m_textStats[nIndex].clear();
99 m_pCtxt->GetComparePaths(di, tFiles);
100 struct change *script10 = nullptr;
101 struct change *script12 = nullptr;
102 struct change *script02 = nullptr;
103 DiffFileData diffdata10, diffdata12, diffdata02;
104 String filepathUnpacked[3];
105 String filepathTransformed[3];
108 // For user chosen plugins, define bAutomaticUnpacker as false and use the chosen infoHandler
109 // but how can we receive the infoHandler ? DirScan actually only
110 // returns info, but can not use file dependent information.
112 // Transformation happens here
113 // text used for automatic mode : plugin filter must match it
114 String filteredFilenames = CDiffContext::GetFilteredFilenames(tFiles);
116 PackingInfo * infoUnpacker = nullptr;
117 PrediffingInfo * infoPrediffer = nullptr;
119 // Get existing or new plugin infos
120 if (m_pCtxt->m_piPluginInfos != nullptr)
121 m_pCtxt->FetchPluginInfos(filteredFilenames, &infoUnpacker,
124 FileTextEncoding encoding[3];
125 bool bForceUTF8 = m_pCtxt->GetCompareOptions(nCompMethod)->m_bIgnoreCase;
127 for (nIndex = 0; nIndex < nDirs; nIndex++)
129 // plugin may alter filepaths to temp copies (which we delete before returning in all cases)
130 filepathUnpacked[nIndex] = tFiles[nIndex];
132 //DiffFileData diffdata; //(filepathTransformed1, filepathTransformed2);
133 // Invoke unpacking plugins
134 if (infoUnpacker && strutils::compare_nocase(filepathUnpacked[nIndex], _T("NUL")) != 0)
136 if (!infoUnpacker->Unpacking(nullptr, filepathUnpacked[nIndex], filteredFilenames, { tFiles[nIndex] }))
137 goto exitPrepAndCompare;
140 // As we keep handles open on unpacked files, Transform() may not delete them.
141 // Unpacked files will be deleted at end of this function.
142 filepathTransformed[nIndex] = filepathUnpacked[nIndex];
144 encoding[nIndex] = codepage_detect::Guess(filepathTransformed[nIndex], m_pCtxt->m_iGuessEncodingType);
145 m_diffFileData.m_FileLocation[nIndex].encoding = encoding[nIndex];
148 if (!std::equal(encoding + 1, encoding + nDirs, encoding))
150 codepage = bForceUTF8 ? CP_UTF8 : (encoding[0].m_unicoding ? CP_UTF8 : encoding[0].m_codepage);
151 for (nIndex = 0; nIndex < nDirs; nIndex++)
153 // Invoke prediff'ing plugins
154 if (!m_diffFileData.Filepath_Transform(bForceUTF8, encoding[nIndex], filepathUnpacked[nIndex], filepathTransformed[nIndex], filteredFilenames, *infoPrediffer))
155 goto exitPrepAndCompare;
158 // If options are binary equivalent, we could check for filesize
159 // difference here, and bail out if files are clearly different
160 // But, then we don't know if file is ascii or binary, and this
161 // affects behavior (also, we don't have an icon for unknown type)
163 // Actually compare the files
164 // `diffutils_compare_files()` is a fairly thin front-end to GNU diffutils
166 if (tFiles.GetSize() == 2)
168 m_diffFileData.SetDisplayFilepaths(tFiles[0], tFiles[1]); // store true names for diff utils patch file
169 // This opens & fstats both files (if it succeeds)
170 if (!m_diffFileData.OpenFiles(filepathTransformed[0], filepathTransformed[1]))
171 goto exitPrepAndCompare;
175 diffdata10.SetDisplayFilepaths(tFiles[1], tFiles[0]); // store true names for diff utils patch file
176 diffdata12.SetDisplayFilepaths(tFiles[1], tFiles[2]); // store true names for diff utils patch file
177 diffdata02.SetDisplayFilepaths(tFiles[0], tFiles[2]); // store true names for diff utils patch file
179 if (!diffdata10.OpenFiles(filepathTransformed[1], filepathTransformed[0]))
180 goto exitPrepAndCompare;
182 if (!diffdata12.OpenFiles(filepathTransformed[1], filepathTransformed[2]))
183 goto exitPrepAndCompare;
185 if (!diffdata02.OpenFiles(filepathTransformed[0], filepathTransformed[2]))
186 goto exitPrepAndCompare;
189 // If either file is larger than limit compare files by quick contents
190 // This allows us to (faster) compare big binary files
191 if (nCompMethod == CMP_CONTENT &&
192 (di.diffFileInfo[0].size > m_pCtxt->m_nQuickCompareLimit ||
193 di.diffFileInfo[1].size > m_pCtxt->m_nQuickCompareLimit ||
194 (nDirs > 2 && di.diffFileInfo[2].size > m_pCtxt->m_nQuickCompareLimit)))
196 nCompMethod = CMP_QUICK_CONTENT;
199 if (nCompMethod == CMP_CONTENT)
201 if (m_pDiffUtilsEngine == nullptr)
203 m_pDiffUtilsEngine.reset(new CompareEngines::DiffUtils());
204 m_pDiffUtilsEngine->SetCodepage(codepage);
205 m_pDiffUtilsEngine->SetCompareOptions(*m_pCtxt->GetCompareOptions(CMP_CONTENT));
206 if (m_pCtxt->m_pFilterList != nullptr)
207 m_pDiffUtilsEngine->SetFilterList(m_pCtxt->m_pFilterList.get());
209 m_pDiffUtilsEngine->ClearFilterList();
210 if (m_pCtxt->m_pSubstitutionList != nullptr)
211 m_pDiffUtilsEngine->SetSubstitutionList(m_pCtxt->m_pSubstitutionList);
213 m_pDiffUtilsEngine->ClearSubstitutionList();
215 if (tFiles.GetSize() == 2)
217 m_pDiffUtilsEngine->SetFileData(2, m_diffFileData.m_inf);
218 code = m_pDiffUtilsEngine->diffutils_compare_files();
219 m_pDiffUtilsEngine->GetDiffCounts(m_ndiffs, m_ntrivialdiffs);
220 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[0]);
221 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[1]);
223 // If unique item, it was being compared to itself to determine encoding
224 // and the #diffs is invalid
225 if (di.diffcode.isSideSecondOnly() || di.diffcode.isSideFirstOnly())
227 m_ndiffs = CDiffContext::DIFFS_UNKNOWN;
228 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN;
234 int bin_flag10 = 0, bin_flag12 = 0, bin_flag02 = 0;
236 m_pDiffUtilsEngine->SetFileData(2, diffdata10.m_inf);
237 bRet = m_pDiffUtilsEngine->Diff2Files(&script10, 0, &bin_flag10, false, nullptr);
238 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[1]);
239 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[0]);
241 m_pDiffUtilsEngine->SetFileData(2, diffdata12.m_inf);
242 bRet = m_pDiffUtilsEngine->Diff2Files(&script12, 0, &bin_flag12, false, nullptr);
243 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[1]);
244 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[2]);
246 m_pDiffUtilsEngine->SetFileData(2, diffdata02.m_inf);
247 bRet = m_pDiffUtilsEngine->Diff2Files(&script02, 0, &bin_flag02, false, nullptr);
248 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[0]);
249 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[2]);
251 code = DIFFCODE::FILE;
253 String Ext = tFiles[0];
254 size_t PosOfDot = Ext.rfind('.');
255 if (PosOfDot != String::npos)
256 Ext.erase(0, PosOfDot + 1);
263 dw.SetCompareFiles(tFiles);
264 dw.SetOptions(m_pCtxt->GetOptions());
265 dw.SetFilterList(m_pCtxt->m_pFilterList.get());
266 dw.SetSubstitutionList(m_pCtxt->m_pSubstitutionList);
267 dw.SetFilterCommentsSourceDef(Ext);
268 dw.SetCreateDiffList(&diffList);
269 dw.LoadWinMergeDiffsFromDiffUtilsScript3(
271 diffdata10.m_inf, diffdata12.m_inf);
272 m_ndiffs = diffList.GetSignificantDiffs();
273 m_ntrivialdiffs = diffList.GetSize() - m_ndiffs;
275 if (m_ndiffs > 0 || bin_flag10 < 0 || bin_flag12 < 0)
276 code |= DIFFCODE::DIFF;
278 code |= DIFFCODE::SAME;
279 if (bin_flag10 || bin_flag12)
280 code |= DIFFCODE::BIN;
282 code |= DIFFCODE::TEXT;
284 if ((code & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF)
286 if ((code & DIFFCODE::TEXTFLAGS) == DIFFCODE::TEXT)
288 if (script12 == nullptr)
289 code |= DIFFCODE::DIFF1STONLY;
290 else if (script02 == nullptr)
291 code |= DIFFCODE::DIFF2NDONLY;
292 else if (script10 == nullptr)
293 code |= DIFFCODE::DIFF3RDONLY;
298 code |= DIFFCODE::DIFF1STONLY;
299 else if (bin_flag02 > 0)
300 code |= DIFFCODE::DIFF2NDONLY;
301 else if (bin_flag10 > 0)
302 code |= DIFFCODE::DIFF3RDONLY;
306 // If unique item, it was being compared to itself to determine encoding
307 // and the #diffs is invalid
308 if (di.diffcode.isSideFirstOnly() || di.diffcode.isSideSecondOnly() || di.diffcode.isSideThirdOnly())
310 m_ndiffs = CDiffContext::DIFFS_UNKNOWN;
311 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN;
314 dw.FreeDiffUtilsScript(script10);
315 dw.FreeDiffUtilsScript(script12);
316 dw.FreeDiffUtilsScript(script02);
320 else if (nCompMethod == CMP_QUICK_CONTENT)
322 // use our own byte-by-byte compare
323 if (m_pByteCompare == nullptr)
325 m_pByteCompare.reset(new ByteCompare());
326 m_pByteCompare->SetCompareOptions(*m_pCtxt->GetCompareOptions(CMP_QUICK_CONTENT));
328 m_pByteCompare->SetAdditionalOptions(m_pCtxt->m_bStopAfterFirstDiff);
329 m_pByteCompare->SetAbortable(m_pCtxt->GetAbortable());
331 if (tFiles.GetSize() == 2)
333 m_pByteCompare->SetFileData(2, m_diffFileData.m_inf);
335 // use our own byte-by-byte compare
336 code = m_pByteCompare->CompareFiles(m_diffFileData.m_FileLocation);
338 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[0]);
339 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[1]);
341 // Quick contents doesn't know about diff counts
342 // Set to special value to indicate invalid
343 m_ndiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
344 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
349 m_pByteCompare->SetFileData(2, diffdata10.m_inf);
351 // use our own byte-by-byte compare
352 int code10 = m_pByteCompare->CompareFiles(diffdata10.m_FileLocation);
354 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[1]);
355 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[0]);
358 m_pByteCompare->SetFileData(2, diffdata12.m_inf);
360 // use our own byte-by-byte compare
361 int code12 = m_pByteCompare->CompareFiles(diffdata12.m_FileLocation);
363 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[1]);
364 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[2]);
367 m_pByteCompare->SetFileData(2, diffdata02.m_inf);
369 // use our own byte-by-byte compare
370 int code02 = m_pByteCompare->CompareFiles(diffdata02.m_FileLocation);
372 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[0]);
373 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[2]);
375 code = DIFFCODE::FILE;
376 if (DIFFCODE::isResultError(code10) || DIFFCODE::isResultError(code12) || DIFFCODE::isResultError(code02))
377 code |= DIFFCODE::CMPERR;
378 if ((code10 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF || (code12 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF)
379 code |= DIFFCODE::DIFF;
381 code |= DIFFCODE::SAME;
382 if ((code10 & DIFFCODE::TEXTFLAGS) == DIFFCODE::TEXT &&
383 (code12 & DIFFCODE::TEXTFLAGS) == DIFFCODE::TEXT)
384 code |= DIFFCODE::TEXT;
386 code |= DIFFCODE::BIN;
387 if ((code10 & DIFFCODE::TEXTFLAGS) == (DIFFCODE::BIN | DIFFCODE::BINSIDE1))
388 code |= DIFFCODE::BINSIDE2;
389 if ((code10 & DIFFCODE::TEXTFLAGS) == (DIFFCODE::BIN | DIFFCODE::BINSIDE2))
390 code |= DIFFCODE::BINSIDE1;
391 if ((code12 & DIFFCODE::TEXTFLAGS) == (DIFFCODE::BIN | DIFFCODE::BINSIDE2))
392 code |= DIFFCODE::BINSIDE3;
393 if ((code & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF)
395 if ((code12 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::SAME)
396 code |= DIFFCODE::DIFF1STONLY;
397 else if ((code02 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::SAME)
398 code |= DIFFCODE::DIFF2NDONLY;
399 else if ((code10 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::SAME)
400 code |= DIFFCODE::DIFF3RDONLY;
403 // Quick contents doesn't know about diff counts
404 // Set to special value to indicate invalid
405 m_ndiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
406 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
410 m_diffFileData.Reset();
415 // delete the temp files after comparison
416 if (filepathTransformed[0] != filepathUnpacked[0] && !filepathTransformed[0].empty())
417 try { TFile(filepathTransformed[0]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathTransformed[0])); }
418 if (filepathTransformed[1] != filepathUnpacked[1] && !filepathTransformed[1].empty())
419 try { TFile(filepathTransformed[1]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathTransformed[1])); }
420 if (nDirs > 2 && filepathTransformed[2] != filepathUnpacked[2] && !filepathTransformed[2].empty())
421 try { TFile(filepathTransformed[2]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathTransformed[2])); }
422 if (filepathUnpacked[0] != tFiles[0] && !filepathUnpacked[0].empty())
423 try { TFile(filepathUnpacked[0]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathUnpacked[0])); }
424 if (filepathUnpacked[1] != tFiles[1] && !filepathUnpacked[1].empty())
425 try { TFile(filepathUnpacked[1]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathUnpacked[1])); }
426 if (nDirs > 2 && filepathUnpacked[2] != tFiles[2] && !filepathUnpacked[2].empty())
427 try { TFile(filepathUnpacked[2]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathUnpacked[2])); }
429 // When comparing empty file and nonexistent file, `DIFFCODE::SAME` flag is set to the variable `code`, so change the flag to `DIFFCODE::DIFF`
430 // Also when disabling ignore codepage option and the encodings of files are not equal, change the flag to `DIFFCODE::DIFF even if `DIFFCODE::SAME` flag is set to the variable `code`
431 if (!di.diffcode.existAll() || (!m_pCtxt->m_bIgnoreCodepage && !std::equal(encoding + 1, encoding + nDirs, encoding)))
432 code = (code & ~DIFFCODE::COMPAREFLAGS) | DIFFCODE::DIFF;
434 else if (nCompMethod == CMP_BINARY_CONTENT)
436 if (m_pBinaryCompare == nullptr)
437 m_pBinaryCompare.reset(new BinaryCompare());
438 m_pBinaryCompare->SetAbortable(m_pCtxt->GetAbortable());
440 m_pCtxt->GetComparePaths(di, tFiles);
441 code = m_pBinaryCompare->CompareFiles(tFiles, di);
443 else if (nCompMethod == CMP_DATE || nCompMethod == CMP_DATE_SIZE || nCompMethod == CMP_SIZE)
445 if (m_pTimeSizeCompare == nullptr)
446 m_pTimeSizeCompare.reset(new TimeSizeCompare());
448 m_pTimeSizeCompare->SetAdditionalOptions(m_pCtxt->m_bIgnoreSmallTimeDiff);
449 code = m_pTimeSizeCompare->CompareFiles(nCompMethod, m_pCtxt->GetCompareDirs(), di);
451 else if (nCompMethod == CMP_IMAGE_CONTENT)
453 if (!m_pImageCompare)
455 m_pImageCompare.reset(new ImageCompare());
456 m_pImageCompare->SetColorDistanceThreshold(m_pCtxt->m_dColorDistanceThreshold);
460 m_pCtxt->GetComparePaths(di, tFiles);
461 code = DIFFCODE::IMAGE | m_pImageCompare->CompareFiles(tFiles, di);
465 // Print error since we should have handled by date compare earlier
466 throw "Invalid compare type, DiffFileData can't handle it";