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"
25 using CompareEngines::ByteCompare;
26 using CompareEngines::BinaryCompare;
27 using CompareEngines::TimeSizeCompare;
29 static void GetComparePaths(CDiffContext * pCtxt, const DIFFITEM &di, PathContext & files);
31 FolderCmp::FolderCmp()
32 : m_pDiffUtilsEngine(nullptr)
33 , m_pByteCompare(nullptr)
34 , m_pBinaryCompare(nullptr)
35 , m_pTimeSizeCompare(nullptr)
36 , m_ndiffs(CDiffContext::DIFFS_UNKNOWN)
37 , m_ntrivialdiffs(CDiffContext::DIFFS_UNKNOWN)
41 FolderCmp::~FolderCmp()
45 bool FolderCmp::RunPlugins(CDiffContext * pCtxt, PluginsContext * plugCtxt, String &errStr)
51 void FolderCmp::CleanupAfterPlugins(PluginsContext *plugCtxt)
56 * @brief Prepare files (run plugins) & compare them, and return diffcode.
57 * This is function to compare two files in folder compare. It is not used in
59 * @param [in] pCtxt Pointer to compare context.
60 * @param [in, out] di Compared files with associated data.
61 * @return Compare result code.
63 int FolderCmp::prepAndCompareFiles(CDiffContext * pCtxt, DIFFITEM &di)
66 int nCompMethod = pCtxt->GetCompareMethod();
68 unsigned code = DIFFCODE::FILE | DIFFCODE::CMPERR;
70 if (nCompMethod == CMP_CONTENT ||
71 nCompMethod == CMP_QUICK_CONTENT)
73 int nDirs = pCtxt->GetCompareDirs();
76 for (nIndex = 0; nIndex < nDirs; nIndex++)
77 m_diffFileData.m_textStats[nIndex].clear();
80 GetComparePaths(pCtxt, di, tFiles);
81 struct change *script10 = nullptr;
82 struct change *script12 = nullptr;
83 struct change *script02 = nullptr;
84 FolderCmp diffdata10, diffdata12, diffdata02;
85 String filepathUnpacked[3];
86 String filepathTransformed[3];
89 // For user chosen plugins, define bAutomaticUnpacker as false and use the chosen infoHandler
90 // but how can we receive the infoHandler ? DirScan actually only
91 // returns info, but can not use file dependent information.
93 // Transformation happens here
94 // text used for automatic mode : plugin filter must match it
95 String filteredFilenames = strutils::join(tFiles.begin(), tFiles.end(), _T("|"));
97 PackingInfo * infoUnpacker = nullptr;
98 PrediffingInfo * infoPrediffer = nullptr;
100 // Get existing or new plugin infos
101 if (pCtxt->m_piPluginInfos != nullptr)
102 pCtxt->FetchPluginInfos(filteredFilenames, &infoUnpacker,
105 FileTextEncoding encoding[3];
106 bool bForceUTF8 = pCtxt->GetCompareOptions(nCompMethod)->m_bIgnoreCase;
108 for (nIndex = 0; nIndex < nDirs; nIndex++)
110 // plugin may alter filepaths to temp copies (which we delete before returning in all cases)
111 filepathUnpacked[nIndex] = tFiles[nIndex];
113 //DiffFileData diffdata; //(filepathTransformed1, filepathTransformed2);
114 // Invoke unpacking plugins
115 if (infoUnpacker && strutils::compare_nocase(filepathUnpacked[nIndex], _T("NUL")) != 0)
117 if (!FileTransform::Unpacking(infoUnpacker, filepathUnpacked[nIndex], filteredFilenames))
118 goto exitPrepAndCompare;
120 // we use the same plugins for both files, so they must be defined before second file
121 assert(!infoUnpacker->m_PluginOrPredifferMode != PLUGIN_MANUAL);
124 // As we keep handles open on unpacked files, Transform() may not delete them.
125 // Unpacked files will be deleted at end of this function.
126 filepathTransformed[nIndex] = filepathUnpacked[nIndex];
128 encoding[nIndex] = GuessCodepageEncoding(filepathTransformed[nIndex], pCtxt->m_iGuessEncodingType);
129 m_diffFileData.m_FileLocation[nIndex].encoding = encoding[nIndex];
132 if (!std::equal(encoding + 1, encoding + nDirs, encoding))
134 codepage = bForceUTF8 ? CP_UTF8 : (encoding[0].m_unicoding ? CP_UTF8 : encoding[0].m_codepage);
135 for (nIndex = 0; nIndex < nDirs; nIndex++)
137 // Invoke prediff'ing plugins
138 if (infoPrediffer && !m_diffFileData.Filepath_Transform(bForceUTF8, encoding[nIndex], filepathUnpacked[nIndex], filepathTransformed[nIndex], filteredFilenames, infoPrediffer))
139 goto exitPrepAndCompare;
142 // If options are binary equivalent, we could check for filesize
143 // difference here, and bail out if files are clearly different
144 // But, then we don't know if file is ascii or binary, and this
145 // affects behavior (also, we don't have an icon for unknown type)
147 // Actually compare the files
148 // `diffutils_compare_files()` is a fairly thin front-end to GNU diffutils
150 if (tFiles.GetSize() == 2)
152 m_diffFileData.SetDisplayFilepaths(tFiles[0], tFiles[1]); // store true names for diff utils patch file
153 // This opens & fstats both files (if it succeeds)
154 if (!m_diffFileData.OpenFiles(filepathTransformed[0], filepathTransformed[1]))
155 goto exitPrepAndCompare;
159 diffdata10.m_diffFileData.SetDisplayFilepaths(tFiles[1], tFiles[0]); // store true names for diff utils patch file
160 diffdata12.m_diffFileData.SetDisplayFilepaths(tFiles[1], tFiles[2]); // store true names for diff utils patch file
161 diffdata02.m_diffFileData.SetDisplayFilepaths(tFiles[0], tFiles[2]); // store true names for diff utils patch file
163 if (!diffdata10.m_diffFileData.OpenFiles(filepathTransformed[1], filepathTransformed[0]))
164 goto exitPrepAndCompare;
166 if (!diffdata12.m_diffFileData.OpenFiles(filepathTransformed[1], filepathTransformed[2]))
167 goto exitPrepAndCompare;
169 if (!diffdata02.m_diffFileData.OpenFiles(filepathTransformed[0], filepathTransformed[2]))
170 goto exitPrepAndCompare;
173 // If either file is larger than limit compare files by quick contents
174 // This allows us to (faster) compare big binary files
175 if (nCompMethod == CMP_CONTENT &&
176 (di.diffFileInfo[0].size > pCtxt->m_nQuickCompareLimit ||
177 di.diffFileInfo[1].size > pCtxt->m_nQuickCompareLimit ||
178 (nDirs > 2 && di.diffFileInfo[2].size > pCtxt->m_nQuickCompareLimit)))
180 nCompMethod = CMP_QUICK_CONTENT;
183 if (nCompMethod == CMP_CONTENT)
185 if (tFiles.GetSize() == 2)
187 if (m_pDiffUtilsEngine == nullptr)
188 m_pDiffUtilsEngine.reset(new CompareEngines::DiffUtils());
189 m_pDiffUtilsEngine->SetCodepage(codepage);
190 bool success = m_pDiffUtilsEngine->SetCompareOptions(
191 *pCtxt->GetCompareOptions(CMP_CONTENT));
194 if (pCtxt->m_pFilterList != nullptr)
195 m_pDiffUtilsEngine->SetFilterList(pCtxt->m_pFilterList.get());
197 m_pDiffUtilsEngine->ClearFilterList();
198 m_pDiffUtilsEngine->SetFilterCommentsManager(pCtxt->m_pFilterCommentsManager);
199 m_pDiffUtilsEngine->SetFileData(2, m_diffFileData.m_inf);
200 code = m_pDiffUtilsEngine->diffutils_compare_files();
201 m_pDiffUtilsEngine->GetDiffCounts(m_ndiffs, m_ntrivialdiffs);
202 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[0]);
203 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[1]);
206 code = DIFFCODE::FILE | DIFFCODE::TEXT | DIFFCODE::CMPERR;
208 // If unique item, it was being compared to itself to determine encoding
209 // and the #diffs is invalid
210 if (di.diffcode.isSideSecondOnly() || di.diffcode.isSideFirstOnly())
212 m_ndiffs = CDiffContext::DIFFS_UNKNOWN;
213 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN;
218 if (m_pDiffUtilsEngine == nullptr)
219 m_pDiffUtilsEngine.reset(new CompareEngines::DiffUtils());
220 m_pDiffUtilsEngine->SetCodepage(codepage);
221 bool success = m_pDiffUtilsEngine->SetCompareOptions(
222 *pCtxt->GetCompareOptions(CMP_CONTENT));
225 if (pCtxt->m_pFilterList != nullptr)
226 m_pDiffUtilsEngine->SetFilterList(pCtxt->m_pFilterList.get());
228 m_pDiffUtilsEngine->ClearFilterList();
229 m_pDiffUtilsEngine->SetFilterCommentsManager(pCtxt->m_pFilterCommentsManager);
232 int bin_flag10 = 0, bin_flag12 = 0, bin_flag02 = 0;
234 m_pDiffUtilsEngine->SetFileData(2, diffdata10.m_diffFileData.m_inf);
235 bRet = m_pDiffUtilsEngine->Diff2Files(&script10, 0, &bin_flag10, false, nullptr);
236 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[1]);
237 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[0]);
239 m_pDiffUtilsEngine->SetFileData(2, diffdata12.m_diffFileData.m_inf);
240 bRet = m_pDiffUtilsEngine->Diff2Files(&script12, 0, &bin_flag12, false, nullptr);
241 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[1]);
242 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[2]);
244 m_pDiffUtilsEngine->SetFileData(2, diffdata02.m_diffFileData.m_inf);
245 bRet = m_pDiffUtilsEngine->Diff2Files(&script02, 0, &bin_flag02, false, nullptr);
246 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[0]);
247 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[2]);
249 code = DIFFCODE::FILE;
256 dw.SetCreateDiffList(&diffList);
257 dw.LoadWinMergeDiffsFromDiffUtilsScript3(
259 diffdata10.m_diffFileData.m_inf, diffdata12.m_diffFileData.m_inf);
260 m_ndiffs = diffList.GetSignificantDiffs();
261 m_ntrivialdiffs = diffList.GetSize() - m_ndiffs;
263 if (m_ndiffs > 0 || bin_flag10 < 0 || bin_flag12 < 0)
264 code |= DIFFCODE::DIFF;
266 code |= DIFFCODE::SAME;
267 if (bin_flag10 || bin_flag12)
268 code |= DIFFCODE::BIN;
270 code |= DIFFCODE::TEXT;
272 if ((code & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF)
274 if ((code & DIFFCODE::TEXTFLAGS) == DIFFCODE::TEXT)
276 if (script12 == nullptr)
277 code |= DIFFCODE::DIFF1STONLY;
278 else if (script02 == nullptr)
279 code |= DIFFCODE::DIFF2NDONLY;
280 else if (script10 == nullptr)
281 code |= DIFFCODE::DIFF3RDONLY;
286 code |= DIFFCODE::DIFF1STONLY;
287 else if (bin_flag02 > 0)
288 code |= DIFFCODE::DIFF2NDONLY;
289 else if (bin_flag10 > 0)
290 code |= DIFFCODE::DIFF3RDONLY;
294 // If unique item, it was being compared to itself to determine encoding
295 // and the #diffs is invalid
296 if (di.diffcode.isSideFirstOnly() || di.diffcode.isSideSecondOnly() || di.diffcode.isSideThirdOnly())
298 m_ndiffs = CDiffContext::DIFFS_UNKNOWN;
299 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN;
302 dw.FreeDiffUtilsScript(script10);
303 dw.FreeDiffUtilsScript(script12);
304 dw.FreeDiffUtilsScript(script02);
307 code = DIFFCODE::FILE | DIFFCODE::CMPERR;
311 else if (nCompMethod == CMP_QUICK_CONTENT)
313 // use our own byte-by-byte compare
314 if (tFiles.GetSize() == 2)
316 if (m_pByteCompare == nullptr)
317 m_pByteCompare.reset(new ByteCompare());
318 bool success = m_pByteCompare->SetCompareOptions(
319 *pCtxt->GetCompareOptions(CMP_QUICK_CONTENT));
323 m_pByteCompare->SetAdditionalOptions(pCtxt->m_bStopAfterFirstDiff);
324 m_pByteCompare->SetAbortable(pCtxt->GetAbortable());
325 m_pByteCompare->SetFileData(2, m_diffFileData.m_inf);
327 // use our own byte-by-byte compare
328 code = m_pByteCompare->CompareFiles(m_diffFileData.m_FileLocation);
330 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[0]);
331 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[1]);
334 code = DIFFCODE::FILE | DIFFCODE::TEXT | DIFFCODE::CMPERR;
336 // Quick contents doesn't know about diff counts
337 // Set to special value to indicate invalid
338 m_ndiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
339 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
343 if (m_pByteCompare == nullptr)
344 m_pByteCompare.reset(new ByteCompare());
345 bool success = m_pByteCompare->SetCompareOptions(
346 *pCtxt->GetCompareOptions(CMP_QUICK_CONTENT));
351 m_pByteCompare->SetAdditionalOptions(pCtxt->m_bStopAfterFirstDiff);
352 m_pByteCompare->SetAbortable(pCtxt->GetAbortable());
355 m_pByteCompare->SetFileData(2, diffdata10.m_diffFileData.m_inf);
357 // use our own byte-by-byte compare
358 int code10 = m_pByteCompare->CompareFiles(diffdata10.m_diffFileData.m_FileLocation);
360 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[1]);
361 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[0]);
364 m_pByteCompare->SetFileData(2, diffdata12.m_diffFileData.m_inf);
366 // use our own byte-by-byte compare
367 int code12 = m_pByteCompare->CompareFiles(diffdata12.m_diffFileData.m_FileLocation);
369 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[1]);
370 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[2]);
373 m_pByteCompare->SetFileData(2, diffdata02.m_diffFileData.m_inf);
375 // use our own byte-by-byte compare
376 int code02 = m_pByteCompare->CompareFiles(diffdata02.m_diffFileData.m_FileLocation);
378 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[0]);
379 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[2]);
381 code = DIFFCODE::FILE;
382 if (DIFFCODE::isResultError(code10) || DIFFCODE::isResultError(code12) || DIFFCODE::isResultError(code02))
383 code |= DIFFCODE::CMPERR;
384 if ((code10 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF || (code12 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF)
385 code |= DIFFCODE::DIFF;
387 code |= DIFFCODE::SAME;
388 if ((code10 & DIFFCODE::TEXTFLAGS) == DIFFCODE::TEXT &&
389 (code12 & DIFFCODE::TEXTFLAGS) == DIFFCODE::TEXT)
390 code |= DIFFCODE::TEXT;
392 code |= DIFFCODE::BIN;
393 if ((code10 & DIFFCODE::TEXTFLAGS) == (DIFFCODE::BIN | DIFFCODE::BINSIDE1))
394 code |= DIFFCODE::BINSIDE2;
395 if ((code10 & DIFFCODE::TEXTFLAGS) == (DIFFCODE::BIN | DIFFCODE::BINSIDE2))
396 code |= DIFFCODE::BINSIDE1;
397 if ((code12 & DIFFCODE::TEXTFLAGS) == (DIFFCODE::BIN | DIFFCODE::BINSIDE2))
398 code |= DIFFCODE::BINSIDE3;
399 if ((code & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF)
401 if ((code12 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::SAME)
402 code |= DIFFCODE::DIFF1STONLY;
403 else if ((code02 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::SAME)
404 code |= DIFFCODE::DIFF2NDONLY;
405 else if ((code10 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::SAME)
406 code |= DIFFCODE::DIFF3RDONLY;
410 code = DIFFCODE::FILE | DIFFCODE::TEXT | DIFFCODE::CMPERR;
412 // Quick contents doesn't know about diff counts
413 // Set to special value to indicate invalid
414 m_ndiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
415 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
419 m_diffFileData.Reset();
420 diffdata10.m_diffFileData.Reset();
421 diffdata12.m_diffFileData.Reset();
422 diffdata02.m_diffFileData.Reset();
424 // delete the temp files after comparison
425 if (filepathTransformed[0] != filepathUnpacked[0])
426 try { TFile(filepathTransformed[0]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathTransformed[0].c_str())); }
427 if (filepathTransformed[1] != filepathUnpacked[1])
428 try { TFile(filepathTransformed[1]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathTransformed[1].c_str())); }
429 if (nDirs > 2 && filepathTransformed[2] != filepathUnpacked[2])
430 try { TFile(filepathTransformed[2]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathTransformed[2].c_str())); }
431 if (filepathUnpacked[0] != tFiles[0])
432 try { TFile(filepathUnpacked[0]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathUnpacked[0].c_str())); }
433 if (filepathUnpacked[1] != tFiles[1])
434 try { TFile(filepathUnpacked[1]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathUnpacked[1].c_str())); }
435 if (nDirs > 2 && filepathUnpacked[2] != tFiles[2])
436 try { TFile(filepathUnpacked[2]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathUnpacked[2].c_str())); }
438 // When comparing empty file and nonexistent file, `DIFFCODE::SAME` flag is set to the variable `code`, so change the flag to `DIFFCODE::DIFF`
439 // 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`
440 if (!di.diffcode.existAll() || (!pCtxt->m_bIgnoreCodepage && !std::equal(encoding + 1, encoding + nDirs, encoding)))
441 code = (code & ~DIFFCODE::COMPAREFLAGS) | DIFFCODE::DIFF;
443 else if (nCompMethod == CMP_BINARY_CONTENT)
445 if (m_pBinaryCompare == nullptr)
446 m_pBinaryCompare.reset(new BinaryCompare());
449 GetComparePaths(pCtxt, di, tFiles);
450 code = m_pBinaryCompare->CompareFiles(tFiles, di);
452 else if (nCompMethod == CMP_DATE || nCompMethod == CMP_DATE_SIZE || nCompMethod == CMP_SIZE)
454 if (m_pTimeSizeCompare == nullptr)
455 m_pTimeSizeCompare.reset(new TimeSizeCompare());
457 m_pTimeSizeCompare->SetAdditionalOptions(pCtxt->m_bIgnoreSmallTimeDiff);
458 code = m_pTimeSizeCompare->CompareFiles(nCompMethod, pCtxt->GetCompareDirs(), di);
462 // Print error since we should have handled by date compare earlier
463 throw "Invalid compare type, DiffFileData can't handle it";
470 * @brief Get actual compared paths from DIFFITEM.
471 * @param [in] pCtx Pointer to compare context.
472 * @param [in] di DiffItem from which the paths are created.
473 * @param [out] left Gets the left compare path.
474 * @param [out] right Gets the right compare path.
475 * @note If item is unique, same path is returned for both.
477 void GetComparePaths(CDiffContext * pCtxt, const DIFFITEM &di, PathContext & tFiles)
479 int nDirs = pCtxt->GetCompareDirs();
481 tFiles.SetSize(nDirs);
483 for (int nIndex = 0; nIndex < nDirs; nIndex++)
485 if (di.diffcode.exists(nIndex))
487 tFiles.SetPath(nIndex,
488 paths::ConcatPath(pCtxt->GetPath(nIndex), di.diffFileInfo[nIndex].GetFile()), false);
492 tFiles.SetPath(nIndex, _T("NUL"), false);