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"
26 using CompareEngines::ByteCompare;
27 using CompareEngines::BinaryCompare;
28 using CompareEngines::TimeSizeCompare;
29 using CompareEngines::ImageCompare;
31 static void GetComparePaths(CDiffContext * pCtxt, const DIFFITEM &di, PathContext & files);
33 FolderCmp::FolderCmp(CDiffContext *pCtxt)
35 , m_pDiffUtilsEngine(nullptr)
36 , m_pByteCompare(nullptr)
37 , m_pBinaryCompare(nullptr)
38 , m_pTimeSizeCompare(nullptr)
39 , m_ndiffs(CDiffContext::DIFFS_UNKNOWN)
40 , m_ntrivialdiffs(CDiffContext::DIFFS_UNKNOWN)
44 FolderCmp::~FolderCmp()
48 bool FolderCmp::RunPlugins(PluginsContext * plugCtxt, String &errStr)
54 void FolderCmp::CleanupAfterPlugins(PluginsContext *plugCtxt)
59 * @brief Prepare files (run plugins) & compare them, and return diffcode.
60 * This is function to compare two files in folder compare. It is not used in
62 * @param [in] pCtxt Pointer to compare context.
63 * @param [in, out] di Compared files with associated data.
64 * @return Compare result code.
66 int FolderCmp::prepAndCompareFiles(DIFFITEM &di)
69 int nCompMethod = m_pCtxt->GetCompareMethod();
70 int nDirs = m_pCtxt->GetCompareDirs();
72 unsigned code = DIFFCODE::FILE | DIFFCODE::CMPERR;
74 if (nCompMethod == CMP_CONTENT || nCompMethod == CMP_QUICK_CONTENT)
76 if ((di.diffFileInfo[0].size > m_pCtxt->m_nBinaryCompareLimit && di.diffFileInfo[0].size != DirItem::FILE_SIZE_NONE) ||
77 (di.diffFileInfo[1].size > m_pCtxt->m_nBinaryCompareLimit && di.diffFileInfo[1].size != DirItem::FILE_SIZE_NONE) ||
78 (nDirs > 2 && di.diffFileInfo[2].size > m_pCtxt->m_nBinaryCompareLimit && di.diffFileInfo[2].size != DirItem::FILE_SIZE_NONE))
80 nCompMethod = CMP_BINARY_CONTENT;
82 else if (m_pCtxt->m_bEnableImageCompare && (
83 di.diffFileInfo[0].size != DirItem::FILE_SIZE_NONE && m_pCtxt->m_pImgfileFilter->includeFile(di.diffFileInfo[0].filename) ||
84 di.diffFileInfo[1].size != DirItem::FILE_SIZE_NONE && m_pCtxt->m_pImgfileFilter->includeFile(di.diffFileInfo[1].filename) ||
85 nDirs > 2 && di.diffFileInfo[2].size != DirItem::FILE_SIZE_NONE && m_pCtxt->m_pImgfileFilter->includeFile(di.diffFileInfo[2].filename)))
87 nCompMethod = CMP_IMAGE_CONTENT;
91 if (nCompMethod == CMP_CONTENT ||
92 nCompMethod == CMP_QUICK_CONTENT)
96 for (nIndex = 0; nIndex < nDirs; nIndex++)
97 m_diffFileData.m_textStats[nIndex].clear();
100 GetComparePaths(m_pCtxt, di, tFiles);
101 struct change *script10 = nullptr;
102 struct change *script12 = nullptr;
103 struct change *script02 = nullptr;
104 DiffFileData diffdata10, diffdata12, diffdata02;
105 String filepathUnpacked[3];
106 String filepathTransformed[3];
109 // For user chosen plugins, define bAutomaticUnpacker as false and use the chosen infoHandler
110 // but how can we receive the infoHandler ? DirScan actually only
111 // returns info, but can not use file dependent information.
113 // Transformation happens here
114 // text used for automatic mode : plugin filter must match it
115 String filteredFilenames = strutils::join(tFiles.begin(), tFiles.end(), _T("|"));
117 PackingInfo * infoUnpacker = nullptr;
118 PrediffingInfo * infoPrediffer = nullptr;
120 // Get existing or new plugin infos
121 if (m_pCtxt->m_piPluginInfos != nullptr)
122 m_pCtxt->FetchPluginInfos(filteredFilenames, &infoUnpacker,
125 FileTextEncoding encoding[3];
126 bool bForceUTF8 = m_pCtxt->GetCompareOptions(nCompMethod)->m_bIgnoreCase;
128 for (nIndex = 0; nIndex < nDirs; nIndex++)
130 // plugin may alter filepaths to temp copies (which we delete before returning in all cases)
131 filepathUnpacked[nIndex] = tFiles[nIndex];
133 //DiffFileData diffdata; //(filepathTransformed1, filepathTransformed2);
134 // Invoke unpacking plugins
135 if (infoUnpacker && strutils::compare_nocase(filepathUnpacked[nIndex], _T("NUL")) != 0)
137 if (!FileTransform::Unpacking(infoUnpacker, nullptr, filepathUnpacked[nIndex], filteredFilenames))
138 goto exitPrepAndCompare;
140 // we use the same plugins for both files, so they must be defined before second file
141 assert(infoUnpacker->m_PluginOrPredifferMode == PLUGIN_MODE::PLUGIN_MANUAL);
144 // As we keep handles open on unpacked files, Transform() may not delete them.
145 // Unpacked files will be deleted at end of this function.
146 filepathTransformed[nIndex] = filepathUnpacked[nIndex];
148 encoding[nIndex] = codepage_detect::Guess(filepathTransformed[nIndex], m_pCtxt->m_iGuessEncodingType);
149 m_diffFileData.m_FileLocation[nIndex].encoding = encoding[nIndex];
152 if (!std::equal(encoding + 1, encoding + nDirs, encoding))
154 codepage = bForceUTF8 ? CP_UTF8 : (encoding[0].m_unicoding ? CP_UTF8 : encoding[0].m_codepage);
155 for (nIndex = 0; nIndex < nDirs; nIndex++)
157 // Invoke prediff'ing plugins
158 if (infoPrediffer && !m_diffFileData.Filepath_Transform(bForceUTF8, encoding[nIndex], filepathUnpacked[nIndex], filepathTransformed[nIndex], filteredFilenames, infoPrediffer))
159 goto exitPrepAndCompare;
162 // If options are binary equivalent, we could check for filesize
163 // difference here, and bail out if files are clearly different
164 // But, then we don't know if file is ascii or binary, and this
165 // affects behavior (also, we don't have an icon for unknown type)
167 // Actually compare the files
168 // `diffutils_compare_files()` is a fairly thin front-end to GNU diffutils
170 if (tFiles.GetSize() == 2)
172 m_diffFileData.SetDisplayFilepaths(tFiles[0], tFiles[1]); // store true names for diff utils patch file
173 // This opens & fstats both files (if it succeeds)
174 if (!m_diffFileData.OpenFiles(filepathTransformed[0], filepathTransformed[1]))
175 goto exitPrepAndCompare;
179 diffdata10.SetDisplayFilepaths(tFiles[1], tFiles[0]); // store true names for diff utils patch file
180 diffdata12.SetDisplayFilepaths(tFiles[1], tFiles[2]); // store true names for diff utils patch file
181 diffdata02.SetDisplayFilepaths(tFiles[0], tFiles[2]); // store true names for diff utils patch file
183 if (!diffdata10.OpenFiles(filepathTransformed[1], filepathTransformed[0]))
184 goto exitPrepAndCompare;
186 if (!diffdata12.OpenFiles(filepathTransformed[1], filepathTransformed[2]))
187 goto exitPrepAndCompare;
189 if (!diffdata02.OpenFiles(filepathTransformed[0], filepathTransformed[2]))
190 goto exitPrepAndCompare;
193 // If either file is larger than limit compare files by quick contents
194 // This allows us to (faster) compare big binary files
195 if (nCompMethod == CMP_CONTENT &&
196 (di.diffFileInfo[0].size > m_pCtxt->m_nQuickCompareLimit ||
197 di.diffFileInfo[1].size > m_pCtxt->m_nQuickCompareLimit ||
198 (nDirs > 2 && di.diffFileInfo[2].size > m_pCtxt->m_nQuickCompareLimit)))
200 nCompMethod = CMP_QUICK_CONTENT;
203 if (nCompMethod == CMP_CONTENT)
205 if (m_pDiffUtilsEngine == nullptr)
207 m_pDiffUtilsEngine.reset(new CompareEngines::DiffUtils());
208 m_pDiffUtilsEngine->SetCodepage(codepage);
209 m_pDiffUtilsEngine->SetCompareOptions(*m_pCtxt->GetCompareOptions(CMP_CONTENT));
210 if (m_pCtxt->m_pFilterList != nullptr)
211 m_pDiffUtilsEngine->SetFilterList(m_pCtxt->m_pFilterList.get());
213 m_pDiffUtilsEngine->ClearFilterList();
214 if (m_pCtxt->m_pSubstitutionList != nullptr)
215 m_pDiffUtilsEngine->SetSubstitutionList(m_pCtxt->m_pSubstitutionList);
217 m_pDiffUtilsEngine->ClearSubstitutionList();
219 if (tFiles.GetSize() == 2)
221 m_pDiffUtilsEngine->SetFileData(2, m_diffFileData.m_inf);
222 code = m_pDiffUtilsEngine->diffutils_compare_files();
223 m_pDiffUtilsEngine->GetDiffCounts(m_ndiffs, m_ntrivialdiffs);
224 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[0]);
225 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[1]);
227 // If unique item, it was being compared to itself to determine encoding
228 // and the #diffs is invalid
229 if (di.diffcode.isSideSecondOnly() || di.diffcode.isSideFirstOnly())
231 m_ndiffs = CDiffContext::DIFFS_UNKNOWN;
232 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN;
238 int bin_flag10 = 0, bin_flag12 = 0, bin_flag02 = 0;
240 m_pDiffUtilsEngine->SetFileData(2, diffdata10.m_inf);
241 bRet = m_pDiffUtilsEngine->Diff2Files(&script10, 0, &bin_flag10, false, nullptr);
242 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[1]);
243 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[0]);
245 m_pDiffUtilsEngine->SetFileData(2, diffdata12.m_inf);
246 bRet = m_pDiffUtilsEngine->Diff2Files(&script12, 0, &bin_flag12, false, nullptr);
247 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[1]);
248 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[2]);
250 m_pDiffUtilsEngine->SetFileData(2, diffdata02.m_inf);
251 bRet = m_pDiffUtilsEngine->Diff2Files(&script02, 0, &bin_flag02, false, nullptr);
252 m_pDiffUtilsEngine->GetTextStats(0, &m_diffFileData.m_textStats[0]);
253 m_pDiffUtilsEngine->GetTextStats(1, &m_diffFileData.m_textStats[2]);
255 code = DIFFCODE::FILE;
257 String Ext = tFiles[0];
258 size_t PosOfDot = Ext.rfind('.');
259 if (PosOfDot != String::npos)
260 Ext.erase(0, PosOfDot + 1);
267 dw.SetCompareFiles(tFiles);
268 dw.SetOptions(m_pCtxt->GetOptions());
269 dw.SetFilterList(m_pCtxt->m_pFilterList.get());
270 dw.SetSubstitutionList(m_pCtxt->m_pSubstitutionList);
271 dw.SetFilterCommentsSourceDef(Ext);
272 dw.SetCreateDiffList(&diffList);
273 dw.LoadWinMergeDiffsFromDiffUtilsScript3(
275 diffdata10.m_inf, diffdata12.m_inf);
276 m_ndiffs = diffList.GetSignificantDiffs();
277 m_ntrivialdiffs = diffList.GetSize() - m_ndiffs;
279 if (m_ndiffs > 0 || bin_flag10 < 0 || bin_flag12 < 0)
280 code |= DIFFCODE::DIFF;
282 code |= DIFFCODE::SAME;
283 if (bin_flag10 || bin_flag12)
284 code |= DIFFCODE::BIN;
286 code |= DIFFCODE::TEXT;
288 if ((code & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF)
290 if ((code & DIFFCODE::TEXTFLAGS) == DIFFCODE::TEXT)
292 if (script12 == nullptr)
293 code |= DIFFCODE::DIFF1STONLY;
294 else if (script02 == nullptr)
295 code |= DIFFCODE::DIFF2NDONLY;
296 else if (script10 == nullptr)
297 code |= DIFFCODE::DIFF3RDONLY;
302 code |= DIFFCODE::DIFF1STONLY;
303 else if (bin_flag02 > 0)
304 code |= DIFFCODE::DIFF2NDONLY;
305 else if (bin_flag10 > 0)
306 code |= DIFFCODE::DIFF3RDONLY;
310 // If unique item, it was being compared to itself to determine encoding
311 // and the #diffs is invalid
312 if (di.diffcode.isSideFirstOnly() || di.diffcode.isSideSecondOnly() || di.diffcode.isSideThirdOnly())
314 m_ndiffs = CDiffContext::DIFFS_UNKNOWN;
315 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN;
318 dw.FreeDiffUtilsScript(script10);
319 dw.FreeDiffUtilsScript(script12);
320 dw.FreeDiffUtilsScript(script02);
324 else if (nCompMethod == CMP_QUICK_CONTENT)
326 // use our own byte-by-byte compare
327 if (m_pByteCompare == nullptr)
329 m_pByteCompare.reset(new ByteCompare());
330 m_pByteCompare->SetCompareOptions(*m_pCtxt->GetCompareOptions(CMP_QUICK_CONTENT));
332 m_pByteCompare->SetAdditionalOptions(m_pCtxt->m_bStopAfterFirstDiff);
333 m_pByteCompare->SetAbortable(m_pCtxt->GetAbortable());
335 if (tFiles.GetSize() == 2)
337 m_pByteCompare->SetFileData(2, m_diffFileData.m_inf);
339 // use our own byte-by-byte compare
340 code = m_pByteCompare->CompareFiles(m_diffFileData.m_FileLocation);
342 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[0]);
343 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[1]);
345 // Quick contents doesn't know about diff counts
346 // Set to special value to indicate invalid
347 m_ndiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
348 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
353 m_pByteCompare->SetFileData(2, diffdata10.m_inf);
355 // use our own byte-by-byte compare
356 int code10 = m_pByteCompare->CompareFiles(diffdata10.m_FileLocation);
358 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[1]);
359 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[0]);
362 m_pByteCompare->SetFileData(2, diffdata12.m_inf);
364 // use our own byte-by-byte compare
365 int code12 = m_pByteCompare->CompareFiles(diffdata12.m_FileLocation);
367 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[1]);
368 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[2]);
371 m_pByteCompare->SetFileData(2, diffdata02.m_inf);
373 // use our own byte-by-byte compare
374 int code02 = m_pByteCompare->CompareFiles(diffdata02.m_FileLocation);
376 m_pByteCompare->GetTextStats(0, &m_diffFileData.m_textStats[0]);
377 m_pByteCompare->GetTextStats(1, &m_diffFileData.m_textStats[2]);
379 code = DIFFCODE::FILE;
380 if (DIFFCODE::isResultError(code10) || DIFFCODE::isResultError(code12) || DIFFCODE::isResultError(code02))
381 code |= DIFFCODE::CMPERR;
382 if ((code10 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF || (code12 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF)
383 code |= DIFFCODE::DIFF;
385 code |= DIFFCODE::SAME;
386 if ((code10 & DIFFCODE::TEXTFLAGS) == DIFFCODE::TEXT &&
387 (code12 & DIFFCODE::TEXTFLAGS) == DIFFCODE::TEXT)
388 code |= DIFFCODE::TEXT;
390 code |= DIFFCODE::BIN;
391 if ((code10 & DIFFCODE::TEXTFLAGS) == (DIFFCODE::BIN | DIFFCODE::BINSIDE1))
392 code |= DIFFCODE::BINSIDE2;
393 if ((code10 & DIFFCODE::TEXTFLAGS) == (DIFFCODE::BIN | DIFFCODE::BINSIDE2))
394 code |= DIFFCODE::BINSIDE1;
395 if ((code12 & DIFFCODE::TEXTFLAGS) == (DIFFCODE::BIN | DIFFCODE::BINSIDE2))
396 code |= DIFFCODE::BINSIDE3;
397 if ((code & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF)
399 if ((code12 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::SAME)
400 code |= DIFFCODE::DIFF1STONLY;
401 else if ((code02 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::SAME)
402 code |= DIFFCODE::DIFF2NDONLY;
403 else if ((code10 & DIFFCODE::COMPAREFLAGS) == DIFFCODE::SAME)
404 code |= DIFFCODE::DIFF3RDONLY;
407 // Quick contents doesn't know about diff counts
408 // Set to special value to indicate invalid
409 m_ndiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
410 m_ntrivialdiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
414 m_diffFileData.Reset();
419 // delete the temp files after comparison
420 if (filepathTransformed[0] != filepathUnpacked[0])
421 try { TFile(filepathTransformed[0]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathTransformed[0])); }
422 if (filepathTransformed[1] != filepathUnpacked[1])
423 try { TFile(filepathTransformed[1]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathTransformed[1])); }
424 if (nDirs > 2 && filepathTransformed[2] != filepathUnpacked[2])
425 try { TFile(filepathTransformed[2]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathTransformed[2])); }
426 if (filepathUnpacked[0] != tFiles[0])
427 try { TFile(filepathUnpacked[0]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathUnpacked[0])); }
428 if (filepathUnpacked[1] != tFiles[1])
429 try { TFile(filepathUnpacked[1]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathUnpacked[1])); }
430 if (nDirs > 2 && filepathUnpacked[2] != tFiles[2])
431 try { TFile(filepathUnpacked[2]).remove(); } catch (...) { LogErrorString(strutils::format(_T("DeleteFile(%s) failed"), filepathUnpacked[2])); }
433 // When comparing empty file and nonexistent file, `DIFFCODE::SAME` flag is set to the variable `code`, so change the flag to `DIFFCODE::DIFF`
434 // 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`
435 if (!di.diffcode.existAll() || (!m_pCtxt->m_bIgnoreCodepage && !std::equal(encoding + 1, encoding + nDirs, encoding)))
436 code = (code & ~DIFFCODE::COMPAREFLAGS) | DIFFCODE::DIFF;
438 else if (nCompMethod == CMP_BINARY_CONTENT)
440 if (m_pBinaryCompare == nullptr)
441 m_pBinaryCompare.reset(new BinaryCompare());
442 m_pBinaryCompare->SetAbortable(m_pCtxt->GetAbortable());
444 GetComparePaths(m_pCtxt, di, tFiles);
445 code = m_pBinaryCompare->CompareFiles(tFiles, di);
447 else if (nCompMethod == CMP_DATE || nCompMethod == CMP_DATE_SIZE || nCompMethod == CMP_SIZE)
449 if (m_pTimeSizeCompare == nullptr)
450 m_pTimeSizeCompare.reset(new TimeSizeCompare());
452 m_pTimeSizeCompare->SetAdditionalOptions(m_pCtxt->m_bIgnoreSmallTimeDiff);
453 code = m_pTimeSizeCompare->CompareFiles(nCompMethod, m_pCtxt->GetCompareDirs(), di);
455 else if (nCompMethod == CMP_IMAGE_CONTENT)
457 if (!m_pImageCompare)
459 m_pImageCompare.reset(new ImageCompare());
460 m_pImageCompare->SetColorDistanceThreshold(m_pCtxt->m_dColorDistanceThreshold);
464 GetComparePaths(m_pCtxt, di, tFiles);
465 code = DIFFCODE::IMAGE | m_pImageCompare->CompareFiles(tFiles, di);
469 // Print error since we should have handled by date compare earlier
470 throw "Invalid compare type, DiffFileData can't handle it";
477 * @brief Get actual compared paths from DIFFITEM.
478 * @param [in] pCtx Pointer to compare context.
479 * @param [in] di DiffItem from which the paths are created.
480 * @param [out] left Gets the left compare path.
481 * @param [out] right Gets the right compare path.
482 * @note If item is unique, same path is returned for both.
484 void GetComparePaths(CDiffContext * pCtxt, const DIFFITEM &di, PathContext & tFiles)
486 int nDirs = pCtxt->GetCompareDirs();
488 tFiles.SetSize(nDirs);
490 for (int nIndex = 0; nIndex < nDirs; nIndex++)
492 if (di.diffcode.exists(nIndex))
494 tFiles.SetPath(nIndex,
495 paths::ConcatPath(pCtxt->GetPath(nIndex), di.diffFileInfo[nIndex].GetFile()), false);
499 tFiles.SetPath(nIndex, _T("NUL"), false);