4 * @brief Implementation of DirScan (q.v.) and helper functions
11 #define POCO_NO_UNWINDOWS 1
12 #include <Poco/Semaphore.h>
13 #include <Poco/Notification.h>
14 #include <Poco/NotificationQueue.h>
15 #include <Poco/Environment.h>
16 #include <Poco/ThreadPool.h>
17 #include <Poco/Thread.h>
18 #include <Poco/Runnable.h>
19 #include <Poco/Mutex.h>
20 #include <Poco/AutoPtr.h>
21 #include <Poco/Stopwatch.h>
22 #include <Poco/Format.h>
23 #include "DiffThread.h"
24 #include "UnicodeString.h"
25 #include "DiffWrapper.h"
26 #include "CompareStats.h"
27 #include "FolderCmp.h"
28 #include "FileFilterHelper.h"
29 #include "IAbortable.h"
31 #include "DirTravel.h"
34 #include "MergeAppCOMClass.h"
36 #include "OptionsDef.h"
37 #include "OptionsMgr.h"
38 #include "PathContext.h"
41 using Poco::NotificationQueue;
42 using Poco::Notification;
45 using Poco::ThreadPool;
47 using Poco::Environment;
48 using Poco::Stopwatch;
50 // Static functions (ie, functions only used locally)
51 static void CompareDiffItem(FolderCmp &fc, DIFFITEM &di);
52 static DIFFITEM *AddToList(const String &sLeftDir, const String &sRightDir, const DirItem *lent, const DirItem *rent,
53 unsigned code, DiffFuncStruct *myStruct, DIFFITEM *parent);
54 static DIFFITEM *AddToList(const String &sDir1, const String &sDir2, const String &sDir3, const DirItem *ent1, const DirItem *ent2, const DirItem *ent3,
55 unsigned code, DiffFuncStruct *myStruct, DIFFITEM *parent, int nItems = 3);
56 static void UpdateDiffItem(DIFFITEM &di, bool &bExists, CDiffContext *pCtxt);
57 static int CompareItems(NotificationQueue &queue, DiffFuncStruct *myStruct, DIFFITEM *parentdiffpos);
58 static unsigned GetDirCompareFlags3Way(const DIFFITEM& di);
60 class WorkNotification: public Poco::Notification
63 WorkNotification(DIFFITEM& di, NotificationQueue& queueResult): m_di(di), m_queueResult(queueResult) {}
64 DIFFITEM& data() const { return m_di; }
65 NotificationQueue& queueResult() const { return m_queueResult; }
68 NotificationQueue& m_queueResult;
71 class WorkCompletedNotification: public Poco::Notification
74 explicit WorkCompletedNotification(DIFFITEM& di): m_di(di) {}
75 DIFFITEM& data() const { return m_di; }
80 class DiffWorker: public Runnable
83 DiffWorker(NotificationQueue& queue, CDiffContext *pCtxt, int id):
84 m_queue(queue), m_pCtxt(pCtxt), m_id(id) {}
88 FolderCmp fc(m_pCtxt);
89 // keep the scripts alive during the Rescan
90 // when we exit the thread, we delete this and release the scripts
91 CAssureScriptsForThread scriptsForRescan(new MergeAppCOMClass());
93 AutoPtr<Notification> pNf(m_queue.waitDequeueNotification());
94 while (pNf.get() != nullptr)
96 WorkNotification* pWorkNf = dynamic_cast<WorkNotification*>(pNf.get());
97 if (pWorkNf != nullptr) {
98 m_pCtxt->m_pCompareStats->BeginCompare(&pWorkNf->data(), m_id);
99 if (!m_pCtxt->ShouldAbort())
100 CompareDiffItem(fc, pWorkNf->data());
101 pWorkNf->queueResult().enqueueNotification(new WorkCompletedNotification(pWorkNf->data()));
103 if (m_pCtxt->m_pCompareStats->IsIdleCompareThread(m_id))
105 m_pCtxt->m_pCompareStats->BeginCompare(nullptr, m_id);
106 while (!m_pCtxt->ShouldAbort() && m_pCtxt->m_pCompareStats->IsIdleCompareThread(m_id))
107 Poco::Thread::sleep(10);
110 pNf = m_queue.waitDequeueNotification();
115 NotificationQueue& m_queue;
116 CDiffContext *m_pCtxt;
120 typedef std::shared_ptr<DiffWorker> DiffWorkerPtr;
123 * @brief Collect file- and folder-names to list.
124 * This function walks given folders and adds found subfolders and files into
125 * lists. There are two modes, determined by the @p depth:
126 * - in non-recursive mode we walk only given folders, and add files
127 * contained. Subfolders are added as folder items, not walked into.
128 * - in recursive mode we walk all subfolders and add the files they
131 * Items are tested against file filters in this function.
133 * @param [in] paths Root paths of compare
134 * @param [in] leftsubdir Left side subdirectory under root path
135 * @param [in] bLeftUniq Is left-side folder unique folder?
136 * @param [in] rightsubdir Right side subdirectory under root path
137 * @param [in] bRightUniq Is right-side folder unique folder?
138 * @param [in] myStruct Compare-related data, like context etc.
139 * @param [in] casesensitive Is filename compare case sensitive?
140 * @param [in] depth Levels of subdirectories to scan, -1 scans all
141 * @param [in] parent Folder diff item to be scanned
142 * @param [in] bUniques If true, walk into unique folders.
143 * @return 1 normally, -1 if compare was aborted
145 int DirScan_GetItems(const PathContext &paths, const String subdir[],
146 DiffFuncStruct *myStruct,
147 bool casesensitive, int depth, DIFFITEM *parent,
150 static const tchar_t backslash[] = _T("\\");
151 int nDirs = paths.GetSize();
152 CDiffContext *pCtxt = myStruct->context;
156 std::copy(paths.begin(), paths.end(), sDir);
158 if (!subdir[0].empty())
160 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
162 sDir[nIndex] = paths::ConcatPath(sDir[nIndex], subdir[nIndex]);
163 subprefix[nIndex] = subdir[nIndex] + backslash;
167 DirItemArray dirs[3], aFiles[3];
168 for (int nIndex = 0; nIndex < nDirs; nIndex++)
169 LoadAndSortFiles(sDir[nIndex], &dirs[nIndex], &aFiles[nIndex], casesensitive);
171 // Allow user to abort scanning
172 if (pCtxt->ShouldAbort())
175 // Handle directories
176 // i points to current directory in left list (leftDirs)
177 // j points to current directory in right list (rightDirs)
181 for (nIndex = 0; nIndex < nDirs; nIndex++)
182 if (dirs[nIndex].size() != 0 || aFiles[nIndex].size() != 0) break;
187 DirItemArray::size_type i=0, j=0, k=0;
190 if (pCtxt->ShouldAbort())
193 if (i >= dirs[0].size() && j >= dirs[1].size() && (nDirs < 3 || k >= dirs[2].size()))
196 unsigned nDiffCode = DIFFCODE::DIR;
197 // Comparing directories leftDirs[i].name to rightDirs[j].name
198 if (i<dirs[0].size() && (j==dirs[1].size() || collstr(dirs[0][i].filename, dirs[1][j].filename, casesensitive)<0)
199 && (nDirs < 3 || (k==dirs[2].size() || collstr(dirs[0][i].filename, dirs[2][k].filename, casesensitive)<0) ))
201 nDiffCode |= DIFFCODE::FIRST;
203 else if (j<dirs[1].size() && (i==dirs[0].size() || collstr(dirs[1][j].filename, dirs[0][i].filename, casesensitive)<0)
204 && (nDirs < 3 || (k==dirs[2].size() || collstr(dirs[1][j].filename, dirs[2][k].filename, casesensitive)<0) ))
206 nDiffCode |= DIFFCODE::SECOND;
210 nDiffCode |= DIFFCODE::BOTH;
214 if (k<dirs[2].size() && (i==dirs[0].size() || collstr(dirs[2][k].filename, dirs[0][i].filename, casesensitive)<0)
215 && (j==dirs[1].size() || collstr(dirs[2][k].filename, dirs[1][j].filename, casesensitive)<0) )
217 nDiffCode |= DIFFCODE::THIRD;
219 else if ((i<dirs[0].size() && j<dirs[1].size() && collstr(dirs[0][i].filename, dirs[1][j].filename, casesensitive) == 0)
220 && (k==dirs[2].size() || collstr(dirs[2][k].filename, dirs[0][i].filename, casesensitive) != 0))
222 nDiffCode |= DIFFCODE::FIRST | DIFFCODE::SECOND;
224 else if ((i<dirs[0].size() && k<dirs[2].size() && collstr(dirs[0][i].filename, dirs[2][k].filename, casesensitive) == 0)
225 && (j==dirs[1].size() || collstr(dirs[1][j].filename, dirs[2][k].filename, casesensitive) != 0))
227 nDiffCode |= DIFFCODE::FIRST | DIFFCODE::THIRD;
229 else if ((j<dirs[1].size() && k<dirs[2].size() && collstr(dirs[1][j].filename, dirs[2][k].filename, casesensitive) == 0)
230 && (i==dirs[0].size() || collstr(dirs[0][i].filename, dirs[1][j].filename, casesensitive) != 0))
232 nDiffCode |= DIFFCODE::SECOND | DIFFCODE::THIRD;
236 nDiffCode |= DIFFCODE::ALL;
245 leftnewsub = (nDiffCode & DIFFCODE::FIRST) ? subprefix[0] + dirs[0][i].filename.get() : subprefix[0] + dirs[1][j].filename.get();
246 rightnewsub = (nDiffCode & DIFFCODE::SECOND) ? subprefix[1] + dirs[1][j].filename.get() : subprefix[1] + dirs[0][i].filename.get();
248 // Test against filter so we don't include contents of filtered out directories
249 // Also this is only place we can test for both-sides directories in recursive compare
250 if ((pCtxt->m_piFilterGlobal!=nullptr && !pCtxt->m_piFilterGlobal->includeDir(leftnewsub, rightnewsub)) ||
251 (pCtxt->m_bIgnoreReparsePoints && (
252 (nDiffCode & DIFFCODE::FIRST) && (dirs[0][i].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
253 (nDiffCode & DIFFCODE::SECOND) && (dirs[1][j].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT))
256 nDiffCode |= DIFFCODE::SKIPPED;
260 leftnewsub = subprefix[0];
261 if (nDiffCode & DIFFCODE::FIRST) leftnewsub += dirs[0][i].filename;
262 else if (nDiffCode & DIFFCODE::SECOND) leftnewsub += dirs[1][j].filename;
263 else if (nDiffCode & DIFFCODE::THIRD) leftnewsub += dirs[2][k].filename;
264 middlenewsub = subprefix[1];
265 if (nDiffCode & DIFFCODE::SECOND) middlenewsub += dirs[1][j].filename;
266 else if (nDiffCode & DIFFCODE::FIRST) middlenewsub += dirs[0][i].filename;
267 else if (nDiffCode & DIFFCODE::THIRD) middlenewsub += dirs[2][k].filename;
268 rightnewsub = subprefix[2];
269 if (nDiffCode & DIFFCODE::THIRD) rightnewsub += dirs[2][k].filename;
270 else if (nDiffCode & DIFFCODE::FIRST) rightnewsub += dirs[0][i].filename;
271 else if (nDiffCode & DIFFCODE::SECOND) rightnewsub += dirs[1][j].filename;
273 // Test against filter so we don't include contents of filtered out directories
274 // Also this is only place we can test for both-sides directories in recursive compare
275 if ((pCtxt->m_piFilterGlobal!=nullptr && !pCtxt->m_piFilterGlobal->includeDir(leftnewsub, middlenewsub, rightnewsub)) ||
276 (pCtxt->m_bIgnoreReparsePoints && (
277 (nDiffCode & DIFFCODE::FIRST) && (dirs[0][i].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
278 (nDiffCode & DIFFCODE::SECOND) && (dirs[1][j].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
279 (nDiffCode & DIFFCODE::THIRD) && (dirs[2][k].flags.attributes & FILE_ATTRIBUTE_REPARSE_POINT))
282 nDiffCode |= DIFFCODE::SKIPPED;
289 AddToList(subdir[0], subdir[1],
290 (nDiffCode & DIFFCODE::FIRST ) ? &dirs[0][i] : nullptr,
291 (nDiffCode & DIFFCODE::SECOND) ? &dirs[1][j] : nullptr,
292 nDiffCode, myStruct, parent);
294 AddToList(subdir[0], subdir[1], subdir[2],
295 (nDiffCode & DIFFCODE::FIRST ) ? &dirs[0][i] : nullptr,
296 (nDiffCode & DIFFCODE::SECOND) ? &dirs[1][j] : nullptr,
297 (nDiffCode & DIFFCODE::THIRD ) ? &dirs[2][k] : nullptr,
298 nDiffCode, myStruct, parent);
303 assert(pCtxt->m_bRecursive);
306 DIFFITEM *me = AddToList(subdir[0], subdir[1],
307 (nDiffCode & DIFFCODE::FIRST ) ? &dirs[0][i] : nullptr,
308 (nDiffCode & DIFFCODE::SECOND) ? &dirs[1][j] : nullptr,
309 nDiffCode, myStruct, parent);
310 if ((nDiffCode & DIFFCODE::SKIPPED) == 0 && ((nDiffCode & DIFFCODE::SIDEFLAGS) == DIFFCODE::BOTH || bUniques))
312 // Scan recursively all subdirectories too, we are not adding folders
313 String newsubdir[3] = {leftnewsub, rightnewsub};
314 int result = DirScan_GetItems(paths, newsubdir, myStruct, casesensitive,
315 depth - 1, me, bUniques);
322 DIFFITEM *me = AddToList(subdir[0], subdir[1], subdir[2],
323 (nDiffCode & DIFFCODE::FIRST ) ? &dirs[0][i] : nullptr,
324 (nDiffCode & DIFFCODE::SECOND) ? &dirs[1][j] : nullptr,
325 (nDiffCode & DIFFCODE::THIRD ) ? &dirs[2][k] : nullptr,
326 nDiffCode, myStruct, parent);
327 if ((nDiffCode & DIFFCODE::SKIPPED) == 0 && ((nDiffCode & DIFFCODE::SIDEFLAGS) == DIFFCODE::ALL || bUniques))
329 // Scan recursively all subdirectories too, we are not adding folders
330 String newsubdir[3] = {leftnewsub, middlenewsub, rightnewsub};
331 int result = DirScan_GetItems(paths, newsubdir, myStruct, casesensitive,
332 depth - 1, me, bUniques);
338 if (nDiffCode & DIFFCODE::FIRST)
340 if (nDiffCode & DIFFCODE::SECOND)
342 if (nDiffCode & DIFFCODE::THIRD)
346 // i points to current file in left list (aFiles[0])
347 // j points to current file in right list (aFiles[1])
351 if (pCtxt->ShouldAbort())
355 // Comparing file aFiles[0][i].name to aFiles[1][j].name
356 if (i<aFiles[0].size() && (j==aFiles[1].size() ||
357 collstr(aFiles[0][i].filename, aFiles[1][j].filename, casesensitive) < 0)
359 (k==aFiles[2].size() || collstr(aFiles[0][i].filename, aFiles[2][k].filename, casesensitive)<0) ))
363 const unsigned nDiffCode = DIFFCODE::FIRST | DIFFCODE::FILE;
364 AddToList(subdir[0], subdir[1], &aFiles[0][i], nullptr, nDiffCode, myStruct, parent);
368 const unsigned nDiffCode = DIFFCODE::FIRST | DIFFCODE::FILE;
369 AddToList(subdir[0], subdir[1], subdir[2], &aFiles[0][i], nullptr, nullptr, nDiffCode, myStruct, parent);
371 // Advance left pointer over left-only entry, and then retest with new pointers
375 if (j<aFiles[1].size() && (i==aFiles[0].size() ||
376 collstr(aFiles[0][i].filename, aFiles[1][j].filename, casesensitive) > 0)
378 (k==aFiles[2].size() || collstr(aFiles[1][j].filename, aFiles[2][k].filename, casesensitive)<0) ))
380 const unsigned nDiffCode = DIFFCODE::SECOND | DIFFCODE::FILE;
382 AddToList(subdir[0], subdir[1], nullptr, &aFiles[1][j], nDiffCode, myStruct, parent);
384 AddToList(subdir[0], subdir[1], subdir[2], nullptr, &aFiles[1][j], nullptr, nDiffCode, myStruct, parent);
385 // Advance right pointer over right-only entry, and then retest with new pointers
391 if (k<aFiles[2].size() && (i==aFiles[0].size() ||
392 collstr(aFiles[2][k].filename, aFiles[0][i].filename, casesensitive)<0)
393 && (j==aFiles[1].size() || collstr(aFiles[2][k].filename, aFiles[1][j].filename, casesensitive)<0) )
395 const unsigned nDiffCode = DIFFCODE::THIRD | DIFFCODE::FILE;
396 AddToList(subdir[0], subdir[1], subdir[2], nullptr, nullptr, &aFiles[2][k], nDiffCode, myStruct, parent);
398 // Advance right pointer over right-only entry, and then retest with new pointers
402 if ((i<aFiles[0].size() && j<aFiles[1].size() && collstr(aFiles[0][i].filename, aFiles[1][j].filename, casesensitive) == 0)
403 && (k==aFiles[2].size() || collstr(aFiles[0][i].filename, aFiles[2][k].filename, casesensitive) != 0))
405 const unsigned nDiffCode = DIFFCODE::FIRST | DIFFCODE::SECOND | DIFFCODE::FILE;
406 AddToList(subdir[0], subdir[1], subdir[2], &aFiles[0][i], &aFiles[1][j], nullptr, nDiffCode, myStruct, parent);
411 else if ((i<aFiles[0].size() && k<aFiles[2].size() && collstr(aFiles[0][i].filename, aFiles[2][k].filename, casesensitive) == 0)
412 && (j==aFiles[1].size() || collstr(aFiles[1][j].filename, aFiles[2][k].filename, casesensitive) != 0))
414 const unsigned nDiffCode = DIFFCODE::FIRST | DIFFCODE::THIRD | DIFFCODE::FILE;
415 AddToList(subdir[0], subdir[1], subdir[2], &aFiles[0][i], nullptr, &aFiles[2][k], nDiffCode, myStruct, parent);
420 else if ((j<aFiles[1].size() && k<aFiles[2].size() && collstr(aFiles[1][j].filename, aFiles[2][k].filename, casesensitive) == 0)
421 && (i==aFiles[0].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, casesensitive) != 0))
423 const unsigned nDiffCode = DIFFCODE::SECOND | DIFFCODE::THIRD | DIFFCODE::FILE;
424 AddToList(subdir[0], subdir[1], subdir[2], nullptr, &aFiles[1][j], &aFiles[2][k], nDiffCode, myStruct, parent);
430 if (i<aFiles[0].size())
434 assert(j<aFiles[1].size());
435 const unsigned nDiffCode = DIFFCODE::BOTH | DIFFCODE::FILE;
436 AddToList(subdir[0], subdir[1], &aFiles[0][i], &aFiles[1][j], nDiffCode, myStruct, parent);
443 assert(j<aFiles[1].size());
444 assert(k<aFiles[2].size());
445 const unsigned nDiffCode = DIFFCODE::ALL | DIFFCODE::FILE;
446 AddToList(subdir[0], subdir[1], subdir[2], &aFiles[0][i], &aFiles[1][j], &aFiles[2][k], nDiffCode, myStruct, parent);
456 if (parent != nullptr)
458 for (int nIndex = 0; nIndex < nDirs; ++nIndex)
459 if (parent->diffcode.exists(nIndex) && parent->diffFileInfo[nIndex].size == DirItem::FILE_SIZE_NONE)
460 parent->diffFileInfo[nIndex].size = 0;
462 DIFFITEM *dic = parent->GetFirstChild();
465 for (int nIndex = 0; nIndex < nDirs; ++nIndex)
467 if (dic->diffFileInfo[nIndex].size != DirItem::FILE_SIZE_NONE)
468 parent->diffFileInfo[nIndex].size += dic->diffFileInfo[nIndex].size;
470 dic = dic->GetFwdSiblingLink();
478 * @brief Compare DiffItems in list and add results to compare context.
480 * @param myStruct [in] A structure containing compare-related data.
481 * @param parentdiffpos [in] Position of parent diff item
482 * @return >= 0 number of diff items, -1 if compare was aborted
484 int DirScan_CompareItems(DiffFuncStruct *myStruct, DIFFITEM *parentdiffpos)
486 const int compareMethod = myStruct->context->GetCompareMethod();
489 if (compareMethod == CMP_CONTENT || compareMethod == CMP_QUICK_CONTENT)
491 nworkers = GetOptionsMgr()->GetInt(OPT_CMP_COMPARE_THREADS);
493 nworkers += Environment::processorCount();
494 nworkers = std::clamp(nworkers, 1, static_cast<int>(Environment::processorCount()));
497 ThreadPool threadPool(nworkers, nworkers);
498 std::vector<DiffWorkerPtr> workers;
499 NotificationQueue queue;
500 myStruct->context->m_pCompareStats->SetCompareThreadCount(nworkers);
501 workers.reserve(nworkers);
502 for (int i = 0; i < nworkers; ++i)
504 workers.emplace_back(std::make_shared<DiffWorker>(queue, myStruct->context, i));
505 threadPool.start(*workers[i]);
508 int res = CompareItems(queue, myStruct, parentdiffpos);
510 myStruct->context->m_pCompareStats->SetIdleCompareThreadCount(0);
513 threadPool.joinAll();
518 static int CompareItems(NotificationQueue& queue, DiffFuncStruct *myStruct, DIFFITEM *parentdiffpos)
520 NotificationQueue queueResult;
522 CDiffContext *pCtxt = myStruct->context;
525 bool bCompareFailure = false;
526 if (parentdiffpos == nullptr)
527 myStruct->pSemaphore->wait();
529 int nDirs = pCtxt->GetCompareDirs();
530 DIFFITEM *pos = pCtxt->GetFirstChildDiffPosition(parentdiffpos);
531 while (pos != nullptr)
533 if (pCtxt->ShouldAbort())
536 if (stopwatch.elapsed() > 2000000)
538 int event = CDiffThread::EVENT_COMPARE_PROGRESSED;
539 myStruct->m_listeners.notify(myStruct, event);
542 myStruct->pSemaphore->wait();
543 DIFFITEM *curpos = pos;
544 DIFFITEM &di = pCtxt->GetNextSiblingDiffRefPosition(pos);
545 bool existsalldirs = di.diffcode.existAll();
546 if (di.diffcode.isDirectory() && pCtxt->m_bRecursive)
548 if ((di.diffcode.diffcode & DIFFCODE::CMPERR) != DIFFCODE::CMPERR)
549 { // Only clear DIFF|SAME flags if not CMPERR (eg. both flags together)
550 di.diffcode.diffcode &= ~(DIFFCODE::DIFF | DIFFCODE::SAME);
552 int ndiff = CompareItems(queue, myStruct, curpos);
553 // Propagate sub-directory status to this directory
555 { // There were differences in the sub-directories
556 if (existsalldirs || pCtxt->m_bWalkUniques)
557 di.diffcode.diffcode |= DIFFCODE::DIFF;
562 { // Sub-directories were identical
564 di.diffcode.diffcode |= DIFFCODE::SAME;
565 else if (pCtxt->m_bWalkUniques && !di.diffcode.isResultFiltered())
566 di.diffcode.diffcode |= DIFFCODE::DIFF;
570 { // There were file IO-errors during sub-directory comparison.
571 di.diffcode.diffcode |= DIFFCODE::CMPERR;
572 bCompareFailure = true;
575 if (nDirs == 3 && (di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF && !di.diffcode.isResultFiltered())
577 di.diffcode.diffcode &= ~DIFFCODE::COMPAREFLAGS3WAY;
578 di.diffcode.diffcode |= GetDirCompareFlags3Way(di);
582 queue.enqueueUrgentNotification(new WorkNotification(di, queueResult));
584 queue.enqueueNotification(new WorkNotification(di, queueResult));
587 pCtxt->GetNextSiblingDiffRefPosition(pos);
592 AutoPtr<Notification> pNf(queueResult.waitDequeueNotification());
593 if (pNf.get() == nullptr)
595 WorkCompletedNotification* pWorkCompletedNf = dynamic_cast<WorkCompletedNotification*>(pNf.get());
596 if (pWorkCompletedNf != nullptr) {
597 DIFFITEM &di = pWorkCompletedNf->data();
598 if (di.diffcode.isResultError()) {
599 DIFFITEM *diParent = di.GetParentLink();
600 assert(diParent != nullptr);
601 if (diParent != nullptr)
603 diParent->diffcode.diffcode |= DIFFCODE::CMPERR;
604 bCompareFailure = true;
608 if (di.diffcode.isResultDiff() ||
609 (!di.diffcode.existAll() && !di.diffcode.isResultFiltered()))
615 return bCompareFailure || pCtxt->ShouldAbort() ? -1 : res;
619 * @brief Compare DiffItems in context marked for rescan.
621 * @param myStruct [in,out] A structure containing compare-related data.
622 * @param parentdiffpos [in] Position of parent diff item
623 * @return >= 0 number of diff items, -1 if compare was aborted
625 static int CompareRequestedItems(DiffFuncStruct *myStruct, DIFFITEM *parentdiffpos)
627 CDiffContext *pCtxt = myStruct->context;
630 bool bCompareFailure = false;
631 bool bCompareIndeterminate = false;
632 if (parentdiffpos == nullptr)
633 myStruct->pSemaphore->wait();
635 // Since the collect thread deletes the DiffItems in the rescan by "Refresh selected",
636 // the compare thread process should not be executed until the collect thread process is completed
637 // to avoid accessing the deleted DiffItems.
638 assert(myStruct->nCollectThreadState == CDiffThread::THREAD_COMPLETED);
640 int nDirs = pCtxt->GetCompareDirs();
641 DIFFITEM *pos = pCtxt->GetFirstChildDiffPosition(parentdiffpos);
642 while (pos != nullptr)
644 if (pCtxt->ShouldAbort())
650 DIFFITEM *curpos = pos;
651 DIFFITEM &di = pCtxt->GetNextSiblingDiffRefPosition(pos);
652 bool existsalldirs = di.diffcode.existAll();
653 if (di.diffcode.isDirectory())
655 if (pCtxt->m_bRecursive)
657 di.diffcode.diffcode &= ~(DIFFCODE::DIFF | DIFFCODE::SAME);
658 int ndiff = CompareRequestedItems(myStruct, curpos);
661 if (existsalldirs || pCtxt->m_bWalkUniques)
662 di.diffcode.diffcode |= DIFFCODE::DIFF;
669 di.diffcode.diffcode |= DIFFCODE::SAME;
670 else if (pCtxt->m_bWalkUniques && !di.diffcode.isResultFiltered())
671 di.diffcode.diffcode |= DIFFCODE::DIFF;
675 { // There were file IO-errors during sub-directory comparison.
676 di.diffcode.diffcode |= DIFFCODE::CMPERR;
677 bCompareFailure = true;
681 { // There were files that have not been compared
682 bCompareIndeterminate = true;
685 if (nDirs == 3 && (di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF && !di.diffcode.isResultFiltered())
687 di.diffcode.diffcode &= ~DIFFCODE::COMPAREFLAGS3WAY;
688 di.diffcode.diffcode |= GetDirCompareFlags3Way(di);
694 if (di.diffcode.isScanNeeded())
696 CompareDiffItem(fc, di);
697 if (di.diffcode.isResultError())
699 DIFFITEM *diParent = di.GetParentLink();
700 assert(diParent != nullptr);
701 if (diParent != nullptr)
703 diParent->diffcode.diffcode |= DIFFCODE::CMPERR;
704 bCompareFailure = true;
710 if (di.diffcode.isResultError())
711 bCompareFailure = true;
712 else if (di.diffcode.isResultNone() || di.diffcode.isResultAbort())
713 bCompareIndeterminate = true;
716 if (di.diffcode.isResultDiff() ||
717 (!existsalldirs && !di.diffcode.isResultFiltered()))
720 return bCompareIndeterminate ? -2 : (bCompareFailure ? -1 : res);
723 int DirScan_CompareRequestedItems(DiffFuncStruct *myStruct, DIFFITEM *parentdiffpos)
725 return CompareRequestedItems(myStruct, parentdiffpos);
728 static int markChildrenForRescan(CDiffContext *pCtxt, DIFFITEM *parentdiffpos)
731 DIFFITEM *pos = pCtxt->GetFirstChildDiffPosition(parentdiffpos);
732 while (pos != nullptr)
734 DIFFITEM *curpos = pos;
735 DIFFITEM &di = pCtxt->GetNextSiblingDiffRefPosition(pos);
736 if (di.diffcode.isDirectory())
737 ncount += markChildrenForRescan(pCtxt, curpos);
740 di.diffcode.diffcode |= DIFFCODE::NEEDSCAN;
747 int DirScan_UpdateMarkedItems(DiffFuncStruct *myStruct, DIFFITEM *parentdiffpos)
749 CDiffContext *pCtxt = myStruct->context;
750 DIFFITEM *pos = pCtxt->GetFirstChildDiffPosition(parentdiffpos);
753 while (pos != nullptr)
755 if (pCtxt->ShouldAbort())
757 DIFFITEM *curpos = pos;
758 DIFFITEM &di = pCtxt->GetNextSiblingDiffRefPosition(pos);
759 if (di.diffcode.isScanNeeded())
761 bool bItemsExist = true;
762 UpdateDiffItem(di, bItemsExist, pCtxt);
765 di.DelinkFromSiblings(); // delink from list of Siblings
766 delete &di; // Also delete all Children items
767 continue; // (... because `di` is now invalid)
769 if (!di.diffcode.isDirectory())
772 if (di.diffcode.isDirectory() && pCtxt->m_bRecursive)
774 for (int i = 0; i < pCtxt->GetCompareDirs(); ++i)
775 if (di.diffcode.exists(i))
776 di.diffFileInfo[i].size = 0;
777 if (di.diffcode.isScanNeeded() && !di.diffcode.isResultFiltered())
780 di.diffcode.diffcode &= ~DIFFCODE::NEEDSCAN;
782 bool casesensitive = false;
783 int depth = myStruct->context->m_bRecursive ? -1 : 0;
785 PathContext paths = myStruct->context->GetNormalizedPaths();
786 for (int i = 0; i < pCtxt->GetCompareDirs(); ++i)
787 subdir[i] = di.diffFileInfo[i].GetFile();
788 DirScan_GetItems(paths, subdir, myStruct,
789 casesensitive, depth, &di, myStruct->context->m_bWalkUniques);
790 ncount += markChildrenForRescan(myStruct->context, curpos);
794 ncount += DirScan_UpdateMarkedItems(myStruct, curpos);
797 if (parentdiffpos != nullptr && pCtxt->m_bRecursive)
799 for (int nIndex = 0; nIndex < pCtxt->GetCompareDirs(); ++nIndex)
800 if (curpos->diffFileInfo[nIndex].size != DirItem::FILE_SIZE_NONE)
801 parentdiffpos->diffFileInfo[nIndex].size += curpos->diffFileInfo[nIndex].size;
807 * @brief Update diffitem file/dir infos.
809 * Re-tests dirs/files if sides still exists, and updates infos for
810 * existing sides. This assumes filenames, or paths are not changed.
811 * Since in normal situations (I can think of) they cannot change
812 * after first compare.
814 * @param [in,out] di DiffItem to update.
815 * @param [out] bExists Set to
816 * - true if one of items exists so diffitem is valid
817 * - false if items were deleted, so diffitem is not valid
818 * @param [in] pCtxt Compare context
820 static void UpdateDiffItem(DIFFITEM &di, bool & bExists, CDiffContext *pCtxt)
823 di.diffcode.setSideNone();
824 for (int i = 0; i < pCtxt->GetCompareDirs(); ++i)
826 di.diffFileInfo[i].ClearPartial();
827 if (pCtxt->UpdateInfoFromDiskHalf(di, i))
829 bool bUpdated = false;
830 if (di.diffFileInfo[i].IsDirectory() == di.diffcode.isDirectory())
832 String filepath = paths::ConcatPath(pCtxt->GetPath(i), di.diffFileInfo[i].GetFile());
833 if (di.diffFileInfo[i].UpdateFileName(filepath)) {
834 di.diffcode.diffcode |= DIFFCODE::FIRST << i;
840 di.diffFileInfo[i].ClearPartial();
846 * @brief Compare two diffitems and add results to difflist in context.
848 * This function does the actual compare for previously gathered list of
849 * items. Basically we:
850 * - ignore items matching file filters
851 * - add non-ignored directories (no compare for directory items)
855 * @param [in] di DiffItem to compare
856 * @param [in,out] pCtxt Compare context: contains difflist, encoding info etc.
857 * @todo For date compare, maybe we should use creation date if modification
860 static void CompareDiffItem(FolderCmp &fc, DIFFITEM &di)
862 CDiffContext *const pCtxt = fc.m_pCtxt;
863 int nDirs = pCtxt->GetCompareDirs();
864 // Clear rescan-request flag (not set by all codepaths)
865 di.diffcode.diffcode &= ~DIFFCODE::NEEDSCAN;
866 // Is it a directory?
867 if (di.diffcode.isDirectory())
869 // We don't actually 'compare' directories, just add non-ignored
870 // directories to list.
874 // 1. Test against filters
875 if (pCtxt->m_piFilterGlobal==nullptr ||
876 (nDirs == 2 && pCtxt->m_piFilterGlobal->includeFile(
877 paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename),
878 paths::ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename)
880 (nDirs == 3 && pCtxt->m_piFilterGlobal->includeFile(
881 paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename),
882 paths::ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename),
883 paths::ConcatPath(di.diffFileInfo[2].path, di.diffFileInfo[2].filename)
886 di.diffcode.diffcode |= DIFFCODE::INCLUDED;
887 di.diffcode.diffcode |= fc.prepAndCompareFiles(di);
888 di.nsdiffs = fc.m_ndiffs;
889 di.nidiffs = fc.m_ntrivialdiffs;
891 for (int i = 0; i < nDirs; ++i)
893 // Set text statistics
894 if (di.diffcode.exists(i))
896 di.diffFileInfo[i].m_textStats = fc.m_diffFileData.m_textStats[i];
897 di.diffFileInfo[i].encoding = fc.m_diffFileData.m_FileLocation[i].encoding;
903 di.diffcode.diffcode |= DIFFCODE::SKIPPED;
906 pCtxt->m_pCompareStats->AddItem(di.diffcode.diffcode);
910 * @brief Add one compare item to list.
911 * @param [in] sLeftDir Left subdirectory.
912 * @param [in] sRightDir Right subdirectory.
913 * @param [in] lent Left item data to add.
914 * @param [in] rent Right item data to add.
915 * @param [in] pCtxt Compare context.
916 * @param [in] parent Parent of item to be added
918 static DIFFITEM *AddToList(const String& sLeftDir, const String& sRightDir,
919 const DirItem * lent, const DirItem * rent,
920 unsigned code, DiffFuncStruct *myStruct, DIFFITEM *parent)
922 return AddToList(sLeftDir, sRightDir, sLeftDir, lent, rent, nullptr, code, myStruct, parent, 2);
926 * @brief Add one compare item to list.
928 static DIFFITEM *AddToList(const String& sDir1, const String& sDir2, const String& sDir3,
929 const DirItem *ent1, const DirItem *ent2, const DirItem *ent3,
930 unsigned code, DiffFuncStruct *myStruct, DIFFITEM *parent, int nItems /*= 3*/)
932 // We must store both paths - we cannot get paths later
933 // and we need unique item paths for example when items
934 // change to identical
935 DIFFITEM *di = myStruct->context->AddNewDiff(parent);
937 di->diffFileInfo[0].path = sDir1;
938 di->diffFileInfo[1].path = sDir2;
939 di->diffFileInfo[2].path = sDir3;
943 di->diffFileInfo[0].filename = ent1->filename;
944 di->diffFileInfo[0].mtime = ent1->mtime;
945 di->diffFileInfo[0].ctime = ent1->ctime;
946 di->diffFileInfo[0].size = ent1->size;
947 di->diffFileInfo[0].flags.attributes = ent1->flags.attributes;
951 // Don't break CDirView::DoCopyRightToLeft()
953 di->diffFileInfo[0].filename = ent3->filename;
954 else if (ent2 != nullptr)
955 di->diffFileInfo[0].filename = ent2->filename;
960 di->diffFileInfo[1].filename = ent2->filename;
961 di->diffFileInfo[1].mtime = ent2->mtime;
962 di->diffFileInfo[1].ctime = ent2->ctime;
963 di->diffFileInfo[1].size = ent2->size;
964 di->diffFileInfo[1].flags.attributes = ent2->flags.attributes;
968 // Don't break CDirView::DoCopyLeftToRight()
970 di->diffFileInfo[1].filename = ent1->filename;
971 else if (ent3 != nullptr)
972 di->diffFileInfo[1].filename = ent3->filename;
977 di->diffFileInfo[2].filename = ent3->filename;
978 di->diffFileInfo[2].mtime = ent3->mtime;
979 di->diffFileInfo[2].ctime = ent3->ctime;
980 di->diffFileInfo[2].size = ent3->size;
981 di->diffFileInfo[2].flags.attributes = ent3->flags.attributes;
985 // Don't break CDirView::DoCopyLeftToRight()
987 di->diffFileInfo[2].filename = ent1->filename;
988 else if (ent2 != nullptr)
989 di->diffFileInfo[2].filename = ent2->filename;
993 di->diffcode.diffcode = code;
995 di->diffcode.diffcode = code | DIFFCODE::THREEWAY;
997 if (!myStruct->bMarkedRescan && myStruct->m_fncCollect)
999 myStruct->context->m_pCompareStats->IncreaseTotalItems();
1000 myStruct->pSemaphore->set();
1006 * @brief Get the 3-way compare flag of the specified directory item.
1007 * @param [in] di Directory Item to get the 3-way compare flag.
1008 * @return 3-way compare flag of the specified directory item.
1010 static unsigned GetDirCompareFlags3Way(const DIFFITEM& di)
1012 assert(di.diffcode.isDirectory());
1013 assert((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF && !di.diffcode.isResultFiltered());
1016 unsigned code = DIFFCODE::DIFFALL;
1018 if (di.diffcode.isSideFirstOnly() || di.diffcode.isMissingFirstOnly())
1020 code = DIFFCODE::DIFF1STONLY;
1023 else if (di.diffcode.isSideSecondOnly() || di.diffcode.isMissingSecondOnly())
1025 code = DIFFCODE::DIFF2NDONLY;
1028 else if (di.diffcode.isSideThirdOnly() || di.diffcode.isMissingThirdOnly())
1030 code = DIFFCODE::DIFF3RDONLY;
1034 if (di.HasChildren())
1036 for (DIFFITEM* pdi = di.GetFirstChild(); pdi != nullptr; pdi = pdi->GetFwdSiblingLink())
1038 if ((pdi->diffcode.diffcode & DIFFCODE::COMPAREFLAGS) == DIFFCODE::DIFF && !pdi->diffcode.isResultFiltered())
1042 code = (pdi->diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY);
1049 case DIFFCODE::DIFF1STONLY:
1050 if ((pdi->diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) != DIFFCODE::DIFF1STONLY)
1051 code = DIFFCODE::DIFFALL;
1053 case DIFFCODE::DIFF2NDONLY:
1054 if ((pdi->diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) != DIFFCODE::DIFF2NDONLY)
1055 code = DIFFCODE::DIFFALL;
1057 case DIFFCODE::DIFF3RDONLY:
1058 if ((pdi->diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) != DIFFCODE::DIFF3RDONLY)
1059 code = DIFFCODE::DIFFALL;
1062 code = DIFFCODE::DIFFALL;