summaryrefslogtreecommitdiff
path: root/comparison.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comparison.cpp')
-rw-r--r--comparison.cpp895
1 files changed, 0 insertions, 895 deletions
diff --git a/comparison.cpp b/comparison.cpp
deleted file mode 100644
index f65d5b26..00000000
--- a/comparison.cpp
+++ /dev/null
@@ -1,895 +0,0 @@
-// **************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
-// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
-// **************************************************************************
-
-#include "comparison.h"
-#include <stdexcept>
-#include <numeric>
-#include <zen/perf.h>
-#include <zen/scope_guard.h>
-#include <zen/process_priority.h>
-#include <zen/symlink_target.h>
-#include <zen/format_unit.h>
-#include "algorithm.h"
-#include "lib/parallel_scan.h"
-#include "lib/resolve_path.h"
-#include "lib/dir_exist_async.h"
-#include "lib/binary.h"
-#include "lib/cmp_filetime.h"
-#include "lib/status_handler_impl.h"
-#include "lib/parallel_scan.h"
-
-using namespace zen;
-
-
-std::vector<FolderPairCfg> zen::extractCompareCfg(const MainConfiguration& mainCfg)
-{
- //merge first and additional pairs
- std::vector<FolderPairEnh> allPairs;
- allPairs.push_back(mainCfg.firstPair);
- allPairs.insert(allPairs.end(),
- mainCfg.additionalPairs.begin(), //add additional pairs
- mainCfg.additionalPairs.end());
-
- std::vector<FolderPairCfg> output;
- std::transform(allPairs.begin(), allPairs.end(), std::back_inserter(output),
- [&](const FolderPairEnh& enhPair) -> FolderPairCfg
- {
- const Zstring leftDirFmt = getFormattedDirectoryName(enhPair.leftDirectory); //ensure they end with FILE_NAME_SEPARATOR and replace macros
- const Zstring rightDirFmt = getFormattedDirectoryName(enhPair.rightDirectory); //
-
- return FolderPairCfg(leftDirFmt, rightDirFmt,
-
- enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->compareVar : mainCfg.cmpConfig.compareVar,
- enhPair.altCmpConfig.get() ? enhPair.altCmpConfig->handleSymlinks : mainCfg.cmpConfig.handleSymlinks,
-
- normalizeFilters(mainCfg.globalFilter, enhPair.localFilter),
-
- enhPair.altSyncConfig.get() ? enhPair.altSyncConfig->directionCfg : mainCfg.syncCfg.directionCfg);
- });
- return output;
-}
-
-//------------------------------------------------------------------------------------------
-namespace
-{
-void checkForIncompleteInput(const std::vector<FolderPairCfg>& folderPairsForm, bool& warningInputFieldEmpty, ProcessCallback& callback)
-{
- bool havePartialPair = false;
- bool haveFullPair = false;
-
- std::for_each(folderPairsForm.begin(), folderPairsForm.end(),
- [&](const FolderPairCfg& fpCfg)
- {
- if (fpCfg.leftDirectoryFmt.empty() != fpCfg.rightDirectoryFmt.empty())
- havePartialPair = true;
- else if (!fpCfg.leftDirectoryFmt.empty())
- haveFullPair = true;
- });
-
- if (havePartialPair == haveFullPair) //error if: all empty or exist both full and partial pairs -> support single-dir scenario
- callback.reportWarning(_("A folder input field is empty.") + L" \n\n" +
- _("The corresponding folder will be considered as empty."), warningInputFieldEmpty);
-}
-
-std::set<Zstring, LessFilename> determineExistentDirs(const std::set<Zstring, LessFilename>& dirnames,
- bool allowUserInteraction,
- ProcessCallback& callback)
-{
- std::set<Zstring, LessFilename> dirsEx;
-
- warn_static("retry klappt nicht für [] + env vars!")
-
- tryReportingError([&]
- {
- std::set<Zstring, LessFilename> missing;
- dirsEx = getExistingDirsUpdating(dirnames, missing, allowUserInteraction, callback); //check *all* directories on each try!
- if (!missing.empty())
- {
- std::wstring msg = _("Cannot find the following folders:") + L"\n";
- for (const Zstring& dirname : missing)
- msg += std::wstring(L"\n") + dirname;
- throw FileError(msg, _("You can ignore this error to consider each folder as empty. The folders then will be created automatically during synchronization."));
- }
- }, callback);
-
- return dirsEx;
-}
-
-
-//check whether one side is subdirectory of other side (folder pair wise!)
-//similar check if one directory is read/written by multiple pairs not before beginning of synchronization
-void checkFolderDependency(const std::vector<FolderPairCfg>& folderPairsFmt, bool& warningDependentFolders, ProcessCallback& callback) //returns warning message, empty if all ok
-{
- std::vector<std::pair<Zstring, Zstring>> dependentDirs;
-
- auto dependentDir = [](const Zstring& lhs, const Zstring& rhs)
- {
- return EqualFilename()(Zstring(lhs.c_str(), std::min(lhs.length(), rhs.length())), //note: this is NOT an equivalence relation!
- Zstring(rhs.c_str(), std::min(lhs.length(), rhs.length())));
- };
-
- for (const FolderPairCfg& fpCfg : folderPairsFmt)
- if (!fpCfg.leftDirectoryFmt.empty() && !fpCfg.rightDirectoryFmt.empty()) //empty folders names may be accepted by user
- {
- if (dependentDir(fpCfg.leftDirectoryFmt, fpCfg.rightDirectoryFmt)) //test wheter leftDirectory begins with rightDirectory or the other way round
- dependentDirs.push_back(std::make_pair(fpCfg.leftDirectoryFmt, fpCfg.rightDirectoryFmt));
- }
-
- if (!dependentDirs.empty())
- {
- std::wstring warningMsg = _("The following folders have dependent paths. Be careful when setting up synchronization rules:");
- for (auto it = dependentDirs.begin(); it != dependentDirs.end(); ++it)
- warningMsg += std::wstring(L"\n\n") +
- it->first + L"\n" +
- it->second;
-
- callback.reportWarning(warningMsg, warningDependentFolders);
- }
-}
-
-
-class CmpCallbackImpl : public CompareCallback
-{
-public:
- CmpCallbackImpl(ProcessCallback& pc, Int64& bytesReported) :
- pc_(pc),
- bytesReported_(bytesReported) {}
-
- virtual void updateCompareStatus(Int64 bytesDelta)
- {
- //inform about the (differential) processed amount of data
- pc_.updateProcessedData(0, bytesDelta); //throw()! -> ensure client and service provider are in sync!
- bytesReported_ += bytesDelta; //
-
- pc_.requestUiRefresh(); //may throw
- }
-
-private:
- ProcessCallback& pc_;
- Int64& bytesReported_;
-};
-
-
-bool filesHaveSameContentUpdating(const Zstring& filename1, const Zstring& filename2, Int64 expectedBytesToCmp, ProcessCallback& pc) //throw FileError
-{
- Int64 bytesReported; //amount of bytes that have been compared and communicated to status handler
-
- //error = unexpected increase of total workload
- zen::ScopeGuard guardStatistics = zen::makeGuard([&] { pc.updateTotalData(0, bytesReported); });
-
- CmpCallbackImpl callback(pc, bytesReported);
- bool sameContent = filesHaveSameContent(filename1, filename2, callback); //throw FileError
-
- guardStatistics.dismiss();
-
- //update statistics to consider the real amount of data processed: consider short-cut behavior if first bytes differ!
- if (bytesReported != expectedBytesToCmp)
- pc.updateTotalData(0, bytesReported - expectedBytesToCmp);
-
- return sameContent;
-}
-
-//#############################################################################################################################
-
-class ComparisonBuffer
-{
-public:
- ComparisonBuffer(const std::set<DirectoryKey>& keysToRead, size_t fileTimeTol, ProcessCallback& callback);
-
- //create comparison result table and fill category except for files existing on both sides: undefinedFiles and undefinedLinks are appended!
- std::shared_ptr<BaseDirPair> compareByTimeSize(const FolderPairCfg& fpConfig) const;
- std::list<std::shared_ptr<BaseDirPair>> compareByContent(const std::vector<FolderPairCfg>& workLoad) const;
-
-private:
- ComparisonBuffer(const ComparisonBuffer&);
- ComparisonBuffer& operator=(const ComparisonBuffer&);
-
- std::shared_ptr<BaseDirPair> performComparison(const FolderPairCfg& fpCfg,
- std::vector<FilePair*>& undefinedFiles,
- std::vector<SymlinkPair*>& undefinedLinks) const;
-
- std::map<DirectoryKey, DirectoryValue> directoryBuffer; //contains only *existing* directories
- const size_t fileTimeTolerance;
- ProcessCallback& callback_;
-};
-
-
-ComparisonBuffer::ComparisonBuffer(const std::set<DirectoryKey>& keysToRead,
- size_t fileTimeTol,
- ProcessCallback& callback) :
- fileTimeTolerance(fileTimeTol),
- callback_(callback)
-{
- class CbImpl : public FillBufferCallback
- {
- public:
- CbImpl(ProcessCallback& pcb) :
- callback_(pcb),
- itemsReported(0) {}
-
- virtual void reportStatus(const std::wstring& statusMsg, int itemsTotal)
- {
- callback_.updateProcessedData(itemsTotal - itemsReported, 0); //processed bytes are reported in subfunctions!
- itemsReported = itemsTotal;
-
- callback_.reportStatus(statusMsg); //may throw
- //callback_.requestUiRefresh(); //already called by reportStatus()
- }
-
- virtual HandleError reportError(const std::wstring& msg, size_t retryNumber)
- {
- switch (callback_.reportError(msg, retryNumber))
- {
- case ProcessCallback::IGNORE_ERROR:
- return ON_ERROR_IGNORE;
-
- case ProcessCallback::RETRY:
- return ON_ERROR_RETRY;
- }
-
- assert(false);
- return ON_ERROR_IGNORE;
- }
-
- private:
- ProcessCallback& callback_;
- int itemsReported;
- } cb(callback);
-
- fillBuffer(keysToRead, //in
- directoryBuffer, //out
- cb,
- UI_UPDATE_INTERVAL / 2); //every ~50 ms
-}
-
-
-//--------------------assemble conflict descriptions---------------------------
-
-//const wchar_t arrowLeft [] = L"\u2190";
-//const wchar_t arrowRight[] = L"\u2192"; unicode arrows -> too small
-const wchar_t arrowLeft [] = L"<--";
-const wchar_t arrowRight[] = L"-->";
-
-
-//check for very old dates or date2s in the future
-std::wstring getConflictInvalidDate(const Zstring& fileNameFull, Int64 utcTime)
-{
- return replaceCpy(_("File %x has an invalid date."), L"%x", fmtFileName(fileNameFull)) + L"\n" +
- _("Date:") + L" " + utcToLocalTimeString(utcTime);
-}
-
-
-//check for changed files with same modification date
-std::wstring getConflictSameDateDiffSize(const FilePair& fileObj)
-{
- return replaceCpy(_("Files %x have the same date but a different size."), L"%x", fmtFileName(fileObj.getObjRelativeName())) + L"\n" +
- L" " + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<LEFT_SIDE >()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<LEFT_SIDE>()) + L"\n" +
- L" " + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.getLastWriteTime<RIGHT_SIDE>()) + L" " + _("Size:") + L" " + toGuiString(fileObj.getFileSize<RIGHT_SIDE>());
-}
-
-
-inline
-std::wstring getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj)
-{
- return _("Items differ in attributes only") + L"\n" +
- L" " + arrowLeft + L" " + fmtFileName(fsObj.getShortName<LEFT_SIDE >()) + L"\n" +
- L" " + arrowRight + L" " + fmtFileName(fsObj.getShortName<RIGHT_SIDE>());
-}
-
-
-template <class FileOrLinkPair> inline
-std::wstring getDescrDiffMetaDate(const FileOrLinkPair& fileObj)
-{
- return _("Items differ in attributes only") + L"\n" +
- L" " + arrowLeft + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.template getLastWriteTime<LEFT_SIDE >()) + L"\n" +
- L" " + arrowRight + L" " + _("Date:") + L" " + utcToLocalTimeString(fileObj.template getLastWriteTime<RIGHT_SIDE>());
-}
-
-//-----------------------------------------------------------------------------
-
-void categorizeSymlinkByTime(SymlinkPair& linkObj, size_t fileTimeTolerance)
-{
- //categorize symlinks that exist on both sides
- switch (CmpFileTime::getResult(linkObj.getLastWriteTime<LEFT_SIDE>(),
- linkObj.getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance))
- {
- case CmpFileTime::TIME_EQUAL:
- //Caveat:
- //1. SYMLINK_EQUAL may only be set if short names match in case: InSyncDir's mapping tables use short name as a key! see db_file.cpp
- //2. harmonize with "bool stillInSync()" in algorithm.cpp
-
- if (linkObj.getShortName<LEFT_SIDE>() == linkObj.getShortName<RIGHT_SIDE>())
- linkObj.setCategory<FILE_EQUAL>();
- else
- linkObj.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(linkObj));
- break;
-
- case CmpFileTime::TIME_LEFT_NEWER:
- linkObj.setCategory<FILE_LEFT_NEWER>();
- break;
-
- case CmpFileTime::TIME_RIGHT_NEWER:
- linkObj.setCategory<FILE_RIGHT_NEWER>();
- break;
-
- case CmpFileTime::TIME_LEFT_INVALID:
- linkObj.setCategoryConflict(getConflictInvalidDate(linkObj.getFullName<LEFT_SIDE>(), linkObj.getLastWriteTime<LEFT_SIDE>()));
- break;
-
- case CmpFileTime::TIME_RIGHT_INVALID:
- linkObj.setCategoryConflict(getConflictInvalidDate(linkObj.getFullName<RIGHT_SIDE>(), linkObj.getLastWriteTime<RIGHT_SIDE>()));
- break;
- }
-}
-
-
-std::shared_ptr<BaseDirPair> ComparisonBuffer::compareByTimeSize(const FolderPairCfg& fpConfig) const
-{
- //do basis scan and retrieve files existing on both sides as "compareCandidates"
- std::vector<FilePair*> uncategorizedFiles;
- std::vector<SymlinkPair*> uncategorizedLinks;
- std::shared_ptr<BaseDirPair> output = performComparison(fpConfig, uncategorizedFiles, uncategorizedLinks);
-
- //finish symlink categorization
- std::for_each(uncategorizedLinks.begin(), uncategorizedLinks.end(),
- [&](SymlinkPair* linkObj) { categorizeSymlinkByTime(*linkObj, fileTimeTolerance); });
-
- //categorize files that exist on both sides
- std::for_each(uncategorizedFiles.begin(), uncategorizedFiles.end(),
- [&](FilePair* fileObj)
- {
- switch (CmpFileTime::getResult(fileObj->getLastWriteTime<LEFT_SIDE>(),
- fileObj->getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance))
- {
- case CmpFileTime::TIME_EQUAL:
- //Caveat:
- //1. FILE_EQUAL may only be set if short names match in case: InSyncDir's mapping tables use short name as a key! see db_file.cpp
- //2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile
- //3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::syncTo() in file_hierarchy.cpp
- if (fileObj->getFileSize<LEFT_SIDE>() == fileObj->getFileSize<RIGHT_SIDE>())
- {
- if (fileObj->getShortName<LEFT_SIDE>() == fileObj->getShortName<RIGHT_SIDE>())
- fileObj->setCategory<FILE_EQUAL>();
- else
- fileObj->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*fileObj));
- }
- else
- fileObj->setCategoryConflict(getConflictSameDateDiffSize(*fileObj)); //same date, different filesize
- break;
-
- case CmpFileTime::TIME_LEFT_NEWER:
- fileObj->setCategory<FILE_LEFT_NEWER>();
- break;
-
- case CmpFileTime::TIME_RIGHT_NEWER:
- fileObj->setCategory<FILE_RIGHT_NEWER>();
- break;
-
- case CmpFileTime::TIME_LEFT_INVALID:
- fileObj->setCategoryConflict(getConflictInvalidDate(fileObj->getFullName<LEFT_SIDE>(), fileObj->getLastWriteTime<LEFT_SIDE>()));
- break;
-
- case CmpFileTime::TIME_RIGHT_INVALID:
- fileObj->setCategoryConflict(getConflictInvalidDate(fileObj->getFullName<RIGHT_SIDE>(), fileObj->getLastWriteTime<RIGHT_SIDE>()));
- break;
- }
- });
- return output;
-}
-
-
-void categorizeSymlinkByContent(SymlinkPair& linkObj, size_t fileTimeTolerance, ProcessCallback& callback)
-{
- //categorize symlinks that exist on both sides
- Zstring targetPathRawL;
- Zstring targetPathRawR;
- Opt<std::wstring> errMsg = tryReportingError([&]
- {
- callback.reportStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtFileName(linkObj.getFullName<LEFT_SIDE>())));
- targetPathRawL = getSymlinkTargetRaw(linkObj.getFullName<LEFT_SIDE>()); //throw FileError
-
- callback.reportStatus(replaceCpy(_("Resolving symbolic link %x"), L"%x", fmtFileName(linkObj.getFullName<RIGHT_SIDE>())));
- targetPathRawR = getSymlinkTargetRaw(linkObj.getFullName<RIGHT_SIDE>()); //throw FileError
- }, callback);
-
- if (errMsg)
- linkObj.setCategoryConflict(*errMsg);
- else
- {
- if (targetPathRawL == targetPathRawR
-#ifdef ZEN_WIN //type of symbolic link is relevant for Windows only
- &&
- dirExists(linkObj.getFullName<LEFT_SIDE >()) == //check if dir-symlink
- dirExists(linkObj.getFullName<RIGHT_SIDE>()) //
-#endif
- )
- {
- //Caveat:
- //1. SYMLINK_EQUAL may only be set if short names match in case: InSyncDir's mapping tables use short name as a key! see db_file.cpp
- //2. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::syncTo() in file_hierarchy.cpp
-
- //symlinks have same "content"
- if (linkObj.getShortName<LEFT_SIDE>() != linkObj.getShortName<RIGHT_SIDE>())
- linkObj.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(linkObj));
- else if (CmpFileTime::getResult(linkObj.getLastWriteTime<LEFT_SIDE>(),
- linkObj.getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance) != CmpFileTime::TIME_EQUAL)
- linkObj.setCategoryDiffMetadata(getDescrDiffMetaDate(linkObj));
- else
- linkObj.setCategory<FILE_EQUAL>();
- }
- else
- linkObj.setCategory<FILE_DIFFERENT>();
- }
-}
-
-
-std::list<std::shared_ptr<BaseDirPair>> ComparisonBuffer::compareByContent(const std::vector<FolderPairCfg>& workLoad) const
-{
- std::list<std::shared_ptr<BaseDirPair>> output;
- if (workLoad.empty())
- return output;
-
- //PERF_START;
- std::vector<FilePair*> undefinedFiles;
-
- //process one folder pair after each other
- for (auto it = workLoad.begin(); it != workLoad.end(); ++it)
- {
- std::vector<SymlinkPair*> uncategorizedLinks;
- //do basis scan and retrieve candidates for binary comparison (files existing on both sides)
-
- output.push_back(performComparison(*it, undefinedFiles, uncategorizedLinks));
-
- //finish symlink categorization
- std::for_each(uncategorizedLinks.begin(), uncategorizedLinks.end(),
- [&](SymlinkPair* linkObj) { categorizeSymlinkByContent(*linkObj, fileTimeTolerance, callback_); });
- }
-
- //finish categorization...
- std::vector<FilePair*> filesToCompareBytewise;
-
- //content comparison of file content happens AFTER finding corresponding files
- //in order to separate into two processes (scanning and comparing)
-
- std::for_each(undefinedFiles.begin(), undefinedFiles.end(),
- [&](FilePair* fileObj)
- {
- //pre-check: files have different content if they have a different filesize (must not be FILE_EQUAL: see InSyncFile)
- if (fileObj->getFileSize<LEFT_SIDE>() != fileObj->getFileSize<RIGHT_SIDE>())
- fileObj->setCategory<FILE_DIFFERENT>();
- else
- filesToCompareBytewise.push_back(fileObj);
- });
-
- const size_t objectsTotal = filesToCompareBytewise.size();
- const UInt64 bytesTotal = //left and right filesizes are equal
- std::accumulate(filesToCompareBytewise.begin(), filesToCompareBytewise.end(), static_cast<UInt64>(0),
- [](UInt64 sum, FilePair* fsObj) { return sum + fsObj->getFileSize<LEFT_SIDE>(); });
-
- callback_.initNewPhase(static_cast<int>(objectsTotal),
- to<Int64>(bytesTotal),
- ProcessCallback::PHASE_COMPARING_CONTENT);
-
- const std::wstring txtComparingContentOfFiles = _("Comparing content of files %x");
-
- //compare files (that have same size) bytewise...
- std::for_each(filesToCompareBytewise.begin(), filesToCompareBytewise.end(),
- [&](FilePair* fileObj)
- {
- callback_.reportStatus(replaceCpy(txtComparingContentOfFiles, L"%x", fmtFileName(fileObj->getObjRelativeName()), false));
-
- //check files that exist in left and right model but have different content
-
- bool haveSameContent = false;
- Opt<std::wstring> errMsg = tryReportingError([&]
- {
- haveSameContent = filesHaveSameContentUpdating(fileObj->getFullName<LEFT_SIDE>(), //throw FileError
- fileObj->getFullName<RIGHT_SIDE>(),
- to<Int64>(fileObj->getFileSize<LEFT_SIDE>()),
- callback_);
-
- callback_.updateProcessedData(1, 0); //processed bytes are reported in subfunctions!
- callback_.requestUiRefresh(); //may throw
- }, callback_);
-
- if (errMsg)
- fileObj->setCategoryConflict(*errMsg);
- else
- {
- if (haveSameContent)
- {
- //Caveat:
- //1. FILE_EQUAL may only be set if short names match in case: InSyncDir's mapping tables use short name as a key! see db_file.cpp
- //2. FILE_EQUAL is expected to mean identical file sizes! See InSyncFile
- //3. harmonize with "bool stillInSync()" in algorithm.cpp, FilePair::syncTo() in file_hierarchy.cpp
- if (fileObj->getShortName<LEFT_SIDE>() != fileObj->getShortName<RIGHT_SIDE>())
- fileObj->setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(*fileObj));
- else if (CmpFileTime::getResult(fileObj->getLastWriteTime<LEFT_SIDE>(), fileObj->getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance) != CmpFileTime::TIME_EQUAL)
- fileObj->setCategoryDiffMetadata(getDescrDiffMetaDate(*fileObj));
- else
- fileObj->setCategory<FILE_EQUAL>();
- }
- else
- fileObj->setCategory<FILE_DIFFERENT>();
- }
- });
- return output;
-}
-
-
-class MergeSides
-{
-public:
- MergeSides(std::vector<FilePair*>& appendUndefinedFileOut,
- std::vector<SymlinkPair*>& appendUndefinedLinkOut) :
- appendUndefinedFile(appendUndefinedFileOut),
- appendUndefinedLink(appendUndefinedLinkOut) {}
-
- void execute(const DirContainer& leftSide, const DirContainer& rightSide, HierarchyObject& output);
-
-private:
- template <SelectedSide side>
- void fillOneSide(const DirContainer& dirCont, HierarchyObject& output);
-
- std::vector<FilePair*>& appendUndefinedFile;
- std::vector<SymlinkPair*>& appendUndefinedLink;
-};
-
-
-template <SelectedSide side>
-void MergeSides::fillOneSide(const DirContainer& dirCont, HierarchyObject& output)
-{
- for (const auto& file : dirCont.files)
- output.addSubFile<side>(file.first, file.second);
-
- for (const auto& link : dirCont.links)
- output.addSubLink<side>(link.first, link.second);
-
- for (const auto& dir : dirCont.dirs)
- {
- DirPair& newDirMap = output.addSubDir<side>(dir.first);
- fillOneSide<side>(dir.second, newDirMap); //recurse
- }
-}
-
-
-//improve merge-perf by over 70% + more natural default sequence
-template <class MapType, class ProcessLeftOnly, class ProcessRightOnly, class ProcessBoth> inline
-void linearMerge(const MapType& mapLeft, const MapType& mapRight, ProcessLeftOnly lo, ProcessRightOnly ro, ProcessBoth bo)
-{
- const auto lessVal = mapLeft.value_comp();
-
- auto iterLeft = mapLeft .begin();
- auto iterRight = mapRight.begin();
-
- auto finishLeft = [&] { std::for_each(iterLeft, mapLeft .end(), lo); };
- auto finishRight = [&] { std::for_each(iterRight, mapRight.end(), ro); };
-
- if (iterLeft == mapLeft .end()) return finishRight();
- if (iterRight == mapRight.end()) return finishLeft();
-
- for (;;)
- if (lessVal(*iterLeft, *iterRight))
- {
- lo(*iterLeft);
- if (++iterLeft == mapLeft.end())
- return finishRight();
- }
- else if (lessVal(*iterRight, *iterLeft))
- {
- ro(*iterRight);
- if (++iterRight == mapRight.end())
- return finishLeft();
- }
- else
- {
- bo(*iterLeft, *iterRight);
- ++iterLeft; //
- ++iterRight; //increment BOTH before checking for end of range!
- if (iterLeft == mapLeft .end()) return finishRight();
- if (iterRight == mapRight.end()) return finishLeft();
- }
-}
-
-
-void MergeSides::execute(const DirContainer& leftSide, const DirContainer& rightSide, HierarchyObject& output)
-{
- //HierarchyObject::addSubFile() must NOT invalidate references used in "appendUndefined"!
-
- typedef const DirContainer::FileList::value_type FileData;
-
- linearMerge(leftSide.files, rightSide.files,
- [&](const FileData& fileLeft ) { output.addSubFile<LEFT_SIDE >(fileLeft .first, fileLeft .second); }, //left only
- [&](const FileData& fileRight) { output.addSubFile<RIGHT_SIDE>(fileRight.first, fileRight.second); }, //right only
-
- [&](const FileData& fileLeft, const FileData& fileRight) //both sides
- {
- FilePair& newEntry = output.addSubFile(fileLeft.first,
- fileLeft.second,
- FILE_EQUAL, //FILE_EQUAL is just a dummy-value here
- fileRight.first,
- fileRight.second);
- appendUndefinedFile.push_back(&newEntry);
- });
-
- //-----------------------------------------------------------------------------------------------
- typedef const DirContainer::LinkList::value_type LinkData;
-
- linearMerge(leftSide.links, rightSide.links,
- [&](const LinkData& linkLeft) { output.addSubLink<LEFT_SIDE >(linkLeft.first, linkLeft.second); }, //left only
- [&](const LinkData& linkRight) { output.addSubLink<RIGHT_SIDE>(linkRight.first, linkRight.second); }, //right only
-
- [&](const LinkData& linkLeft, const LinkData& linkRight) //both sides
- {
- SymlinkPair& newEntry = output.addSubLink(linkLeft.first,
- linkLeft.second,
- SYMLINK_EQUAL, //SYMLINK_EQUAL is just a dummy-value here
- linkRight.first,
- linkRight.second);
- appendUndefinedLink.push_back(&newEntry);
- });
-
- //-----------------------------------------------------------------------------------------------
- typedef const DirContainer::DirList::value_type DirData;
-
- linearMerge(leftSide.dirs, rightSide.dirs,
- [&](const DirData& dirLeft) //left only
- {
- DirPair& newDirMap = output.addSubDir<LEFT_SIDE>(dirLeft.first);
- this->fillOneSide<LEFT_SIDE>(dirLeft.second, newDirMap); //recurse into subdirectories
- },
- [&](const DirData& dirRight) //right only
- {
- DirPair& newDirMap = output.addSubDir<RIGHT_SIDE>(dirRight.first);
- this->fillOneSide<RIGHT_SIDE>(dirRight.second, newDirMap); //recurse into subdirectories
- },
-
- [&](const DirData& dirLeft, const DirData& dirRight) //both sides
- {
- DirPair& newDirMap = output.addSubDir(dirLeft.first, dirRight.first, DIR_EQUAL);
- if (dirLeft.first != dirRight.first)
- newDirMap.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(newDirMap));
-
- execute(dirLeft.second, dirRight.second, newDirMap); //recurse into subdirectories
- });
-}
-
-//mark excluded directories (see fillBuffer()) + remove superfluous excluded subdirectories
-void removeFilteredDirs(HierarchyObject& hierObj, const HardFilter& filterProc)
-{
- //process subdirs recursively
- for (DirPair& dirObj : hierObj.refSubDirs())
- {
- dirObj.setActive(filterProc.passDirFilter(dirObj.getObjRelativeName(), nullptr)); //subObjMightMatch is always true in this context!
- removeFilteredDirs(dirObj, filterProc);
- }
-
- //remove superfluous directories -> note: this does not invalidate "std::vector<FilePair*>& undefinedFiles", since we delete folders only
- //and there is no side-effect for memory positions of FilePair and SymlinkPair thanks to zen::FixedList!
- hierObj.refSubDirs().remove_if([](DirPair& dirObj)
- {
- return !dirObj.isActive() &&
- dirObj.refSubDirs ().empty() &&
- dirObj.refSubLinks().empty() &&
- dirObj.refSubFiles().empty();
- });
-}
-
-
-//create comparison result table and fill category except for files existing on both sides: undefinedFiles and undefinedLinks are appended!
-std::shared_ptr<BaseDirPair> ComparisonBuffer::performComparison(const FolderPairCfg& fpCfg,
- std::vector<FilePair*>& undefinedFiles,
- std::vector<SymlinkPair*>& undefinedLinks) const
-{
- callback_.reportStatus(_("Generating file list..."));
- callback_.forceUiRefresh();
-
- auto getDirValue = [&](const Zstring& dirnameFmt) -> const DirectoryValue*
- {
- auto it = directoryBuffer.find(DirectoryKey(dirnameFmt, fpCfg.filter.nameFilter, fpCfg.handleSymlinks));
- return it != directoryBuffer.end() ? &it->second : nullptr;
- };
-
- const DirectoryValue* bufValueLeft = getDirValue(fpCfg.leftDirectoryFmt);
- const DirectoryValue* bufValueRight = getDirValue(fpCfg.rightDirectoryFmt);
-
- Zstring filterFailedRead;
- auto filterAddFailedDirReads = [&filterFailedRead](const std::set<Zstring>& failedDirReads) //exclude directory child items only!
- {
- //note: relDirPf is empty for base dir, otherwise postfixed! e.g. "subdir\"
- //an empty relDirPf is a pathological case at this point, since determineExistentDirs() already filtered missing base directories!
- std::for_each(failedDirReads .begin(), failedDirReads .end(), [&](const Zstring& relDirPf) { filterFailedRead += relDirPf + Zstr("?*\n"); });
- };
- auto filterAddFailedItemReads = [&filterFailedRead](const std::set<Zstring>& failedItemReads) //exclude item AND (potential) child items!
- {
- std::for_each(failedItemReads.begin(), failedItemReads.end(), [&](const Zstring& relItem ) { filterFailedRead += relItem + Zstr("\n"); });
- };
-
- if (bufValueLeft ) filterAddFailedDirReads(bufValueLeft ->failedDirReads);
- if (bufValueRight) filterAddFailedDirReads(bufValueRight->failedDirReads);
-
- if (bufValueLeft ) filterAddFailedItemReads(bufValueLeft ->failedItemReads);
- if (bufValueRight) filterAddFailedItemReads(bufValueRight->failedItemReads);
-
- std::shared_ptr<BaseDirPair> output = std::make_shared<BaseDirPair>(fpCfg.leftDirectoryFmt,
- bufValueLeft != nullptr, //dir existence must be checked only once: available iff buffer entry exists!
- fpCfg.rightDirectoryFmt,
- bufValueRight != nullptr,
- fpCfg.filter.nameFilter,
- fpCfg.compareVar,
- fileTimeTolerance);
- //PERF_START;
- MergeSides(undefinedFiles, undefinedLinks).execute(bufValueLeft ? bufValueLeft ->dirCont : DirContainer(),
- bufValueRight ? bufValueRight->dirCont : DirContainer(), *output);
- //PERF_STOP;
-
- //##################### in/exclude rows according to filtering #####################
-
- //attention: some excluded directories are still in the comparison result! (see include filter handling!)
- if (!fpCfg.filter.nameFilter->isNull())
- removeFilteredDirs(*output, *fpCfg.filter.nameFilter); //mark excluded directories (see fillBuffer()) + remove superfluous excluded subdirectories
-
- //apply soft filtering (hard filter already applied during traversal!)
- addSoftFiltering(*output, fpCfg.filter.timeSizeFilter);
-
- //handle (user-ignored) traversing errors: just uncheck them, no need to physically delete them from both sides
- if (!filterFailedRead.empty())
- addHardFiltering(*output, filterFailedRead);
-
- //##################################################################################
- return output;
-}
-}
-
-
-void zen::compare(size_t fileTimeTolerance,
- xmlAccess::OptionalDialogs& warnings,
- bool allowUserInteraction,
- bool runWithBackgroundPriority,
- bool createDirLocks,
- std::unique_ptr<LockHolder>& dirLocks,
- const std::vector<FolderPairCfg>& cfgList,
- FolderComparison& output,
- ProcessCallback& callback)
-{
- //specify process and resource handling priorities
- std::unique_ptr<ScheduleForBackgroundProcessing> backgroundPrio;
- if (runWithBackgroundPriority)
- try
- {
- backgroundPrio = make_unique<ScheduleForBackgroundProcessing>(); //throw FileError
- }
- catch (const FileError& e)
- {
- //not an error in this context
- callback.reportInfo(e.toString()); //may throw!
- }
-
- //prevent operating system going into sleep state
- std::unique_ptr<PreventStandby> noStandby;
- try
- {
- noStandby = make_unique<PreventStandby>(); //throw FileError
- }
- catch (const FileError& e)
- {
- //not an error in this context
- callback.reportInfo(e.toString()); //may throw!
- }
-
- //PERF_START;
-
- callback.reportInfo(_("Starting comparison")); //we want some indicator at the very beginning of the log to make sense of "total time"
-
- //init process: keep at beginning so that all gui elements are initialized properly
- callback.initNewPhase(-1, 0, ProcessCallback::PHASE_SCANNING); //it's not known how many files will be scanned => -1 objects
-
- //-------------------some basic checks:------------------------------------------
-
- checkForIncompleteInput(cfgList, warnings.warningInputFieldEmpty, callback);
- checkFolderDependency (cfgList, warnings.warningDependentFolders, callback);
-
- std::set<Zstring, LessFilename> dirnamesExisting;
- //list of directories that are *expected* to be existent (and need to be scanned)!
- //directory existence only checked *once* to avoid race conditions!
- {
- std::set<Zstring, LessFilename> dirnames;
- std::for_each(cfgList.begin(), cfgList.end(),
- [&](const FolderPairCfg& fpCfg)
- {
- dirnames.insert(fpCfg.leftDirectoryFmt);
- dirnames.insert(fpCfg.rightDirectoryFmt);
- });
- dirnamesExisting = determineExistentDirs(dirnames, allowUserInteraction, callback);
- }
-
- auto dirAvailable = [&](const Zstring& dirnameFmt) { return dirnamesExisting.find(dirnameFmt) != dirnamesExisting.end(); };
-
- //-------------------end of basic checks------------------------------------------
-
- try
- {
- //lock (existing) directories before comparison
- if (createDirLocks)
- dirLocks = make_unique<LockHolder>(dirnamesExisting, warnings.warningDirectoryLockFailed, callback);
-
- //------------------- fill directory buffer ---------------------------------------------------
- std::set<DirectoryKey> dirsToRead;
-
- std::for_each(cfgList.begin(), cfgList.end(),
- [&](const FolderPairCfg& fpCfg)
- {
- if (dirAvailable(fpCfg.leftDirectoryFmt)) //only request *currently existing* directories: at this point user is aware that non-ex + empty string are seen as empty folder!
- dirsToRead.insert(DirectoryKey(fpCfg.leftDirectoryFmt, fpCfg.filter.nameFilter, fpCfg.handleSymlinks));
- if (dirAvailable(fpCfg.rightDirectoryFmt))
- dirsToRead.insert(DirectoryKey(fpCfg.rightDirectoryFmt, fpCfg.filter.nameFilter, fpCfg.handleSymlinks));
- });
-
- FolderComparison outputTmp; //write to output as a transaction!
-
- //reduce peak memory by restricting lifetime of ComparisonBuffer to have ended when loading potentially huge InSyncDir instance in redetermineSyncDirection()
- {
- //------------ traverse/read folders -----------------------------------------------------
- ComparisonBuffer cmpBuff(dirsToRead, fileTimeTolerance, callback);
-
- //process binary comparison in one block
- std::vector<FolderPairCfg> workLoadByContent;
- for (const FolderPairCfg& fpCfg : cfgList)
- switch (fpCfg.compareVar)
- {
- case CMP_BY_TIME_SIZE:
- break;
- case CMP_BY_CONTENT:
- workLoadByContent.push_back(fpCfg);
- break;
- }
- std::list<std::shared_ptr<BaseDirPair>> outputByContent = cmpBuff.compareByContent(workLoadByContent);
-
- //write output in order
- for (const FolderPairCfg& fpCfg : cfgList)
- switch (fpCfg.compareVar)
- {
- case CMP_BY_TIME_SIZE:
- outputTmp.push_back(cmpBuff.compareByTimeSize(fpCfg));
- break;
- case CMP_BY_CONTENT:
- assert(!outputByContent.empty());
- if (!outputByContent.empty())
- {
- outputTmp.push_back(outputByContent.front());
- outputByContent.pop_front();
- }
- break;
- }
- }
-
- assert(outputTmp.size() == cfgList.size());
-
- //--------- set initial sync-direction --------------------------------------------------
-
- for (auto j = begin(outputTmp); j != end(outputTmp); ++j)
- {
- const FolderPairCfg& fpCfg = cfgList[j - outputTmp.begin()];
-
- callback.reportStatus(_("Calculating sync directions..."));
- callback.forceUiRefresh();
- zen::redetermineSyncDirection(fpCfg.directionCfg, *j,
- [&](const std::wstring& warning) { callback.reportWarning(warning, warnings.warningDatabaseError); });
- }
-
- //only if everything was processed correctly output is written to!
- //note: output mustn't change during this process to be in sync with GUI grid view!!!
- outputTmp.swap(output);
- }
- catch (const std::bad_alloc& e)
- {
- callback.reportFatalError(_("Out of memory.") + L" " + utfCvrtTo<std::wstring>(e.what()));
- }
- catch (const std::exception& e)
- {
- callback.reportFatalError(utfCvrtTo<std::wstring>(e.what()));
- }
-}
bgstack15