#include "algorithm.h" #include #include #include #include "library/resources.h" #include "shared/fileHandling.h" #include #include "library/filter.h" #include #include "shared/stringConv.h" #include "shared/globalFunctions.h" #include "shared/loki/TypeManip.h" #include "shared/loki/NullType.h" using namespace FreeFileSync; void FreeFileSync::swapGrids(const MainConfiguration& config, FolderComparison& folderCmp) { std::for_each(folderCmp.begin(), folderCmp.end(), boost::bind(&BaseDirMapping::swap, _1)); redetermineSyncDirection(config, folderCmp, NULL); } //---------------------------------------------------------------------------------------------- class Redetermine { public: Redetermine(const SyncConfiguration& configIn) : config(configIn) {} void execute(HierarchyObject& hierObj) const { //process files std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(), *this); //process directories std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), *this); } private: template friend Function std::for_each(Iterator, Iterator, Function); void operator()(FileMapping& fileObj) const { switch (fileObj.getCategory()) { case FILE_LEFT_SIDE_ONLY: fileObj.setSyncDir(config.exLeftSideOnly); break; case FILE_RIGHT_SIDE_ONLY: fileObj.setSyncDir(config.exRightSideOnly); break; case FILE_RIGHT_NEWER: fileObj.setSyncDir(config.rightNewer); break; case FILE_LEFT_NEWER: fileObj.setSyncDir(config.leftNewer); break; case FILE_DIFFERENT: fileObj.setSyncDir(config.different); break; case FILE_CONFLICT: fileObj.setSyncDir(config.conflict); break; case FILE_EQUAL: fileObj.setSyncDir(SYNC_DIR_NONE); break; } } void operator()(DirMapping& dirObj) const { switch (dirObj.getDirCategory()) { case DIR_LEFT_SIDE_ONLY: dirObj.setSyncDir(config.exLeftSideOnly); break; case DIR_RIGHT_SIDE_ONLY: dirObj.setSyncDir(config.exRightSideOnly); break; case DIR_EQUAL: dirObj.setSyncDir(SYNC_DIR_NONE); break; } //recursion execute(dirObj); } const SyncConfiguration& config; }; //--------------------------------------------------------------------------------------------------------------- enum Answer { CHANGE_DETECTED, NO_CHANGE, CANT_SAY_FILTERING_CHANGED }; template class DetectChanges { public: DetectChanges(const DirContainer* dirCont, //DirContainer in sense of a HierarchyObject const FilterProcess& dbFilter); DetectChanges(const DirContainer* dirCont); //DirContainer in sense of a HierarchyObject template Answer detectFileChange(const FileMapping& fileObj) const; struct DirAnswer { DirAnswer(Answer status, const DetectChanges instance) : dirStatus(status), subDirInstance(instance) {} Answer dirStatus; DetectChanges subDirInstance; //not valid if dirStatus == CANT_SAY_FILTERING_CHANGED }; template DirAnswer detectDirChange(const DirMapping& dirObj) const; private: const DirContainer* const dirCont_; //if NULL: did not exist during db creation typename Loki::Select::Result dbFilter_; //filter setting that was used when db was created }; template <> inline DetectChanges::DetectChanges(const DirContainer* dirCont, //DirContainer in sense of a HierarchyObject const FilterProcess& dbFilter) : dirCont_(dirCont), dbFilter_(dbFilter) {} template <> inline DetectChanges::DetectChanges(const DirContainer* dirCont) : //DirContainer in sense of a HierarchyObject dirCont_(dirCont) {} template Answer detectFileChangeSub(const FileMapping& fileObj, const DirContainer* dbDirectory) { if (dbDirectory) { DirContainer::SubFileList::const_iterator j = dbDirectory->getSubFiles().find(fileObj.getObjShortName()); if (j == dbDirectory->getSubFiles().end()) { if (fileObj.isEmpty()) return NO_CHANGE; else return CHANGE_DETECTED; //->create } else { if (fileObj.isEmpty()) return CHANGE_DETECTED; //->delete else { const FileDescriptor& dbData = j->second.getData(); if ( fileObj.getLastWriteTime() == dbData.lastWriteTimeRaw && fileObj.getFileSize() == dbData.fileSize) return NO_CHANGE; else return CHANGE_DETECTED; //->update } } } else { if (fileObj.isEmpty()) return NO_CHANGE; else return CHANGE_DETECTED; //->create } } template <> template inline Answer DetectChanges::detectFileChange(const FileMapping& fileObj) const { return detectFileChangeSub(fileObj, dirCont_); } template <> template inline Answer DetectChanges::detectFileChange(const FileMapping& fileObj) const { //if filtering would have excluded file during database creation, then we can't say anything about its former state if (!dbFilter_.passFileFilter(fileObj.getObjShortName())) return CANT_SAY_FILTERING_CHANGED; return detectFileChangeSub(fileObj, dirCont_); } template Answer detectDirChangeSub(const DirMapping& dirObj, const DirContainer* dbDirectory, const DirContainer*& dbSubDirectory) { if (dbDirectory) { DirContainer::SubDirList::const_iterator j = dbDirectory->getSubDirs().find(dirObj.getObjShortName()); if (j == dbDirectory->getSubDirs().end()) { if (dirObj.isEmpty()) return NO_CHANGE; else return CHANGE_DETECTED; //->create } else { dbSubDirectory = &j->second; if (dirObj.isEmpty()) return CHANGE_DETECTED; //->delete else return NO_CHANGE; } } else { if (dirObj.isEmpty()) return NO_CHANGE; else return CHANGE_DETECTED; //->create } } template <> template DetectChanges::DirAnswer DetectChanges::detectDirChange(const DirMapping& dirObj) const { const DirContainer* dbSubDir = NULL; const Answer answer = detectDirChangeSub(dirObj, dirCont_, dbSubDir); return DirAnswer(answer, DetectChanges(dbSubDir)); } template <> template DetectChanges::DirAnswer DetectChanges::detectDirChange(const DirMapping& dirObj) const { //if filtering would have excluded file during database creation, then we can't say anything about its former state if (!dbFilter_.passDirFilter(dirObj.getObjShortName(), NULL)) return DirAnswer(CANT_SAY_FILTERING_CHANGED, DetectChanges(NULL, dbFilter_)); const DirContainer* dbSubDir = NULL; const Answer answer = detectDirChangeSub(dirObj, dirCont_, dbSubDir); return DirAnswer(answer, DetectChanges(dbSubDir, dbFilter_)); } //---------------------------------------------------------------------------------------------- class SetDirChangedFilter { public: SetDirChangedFilter() : txtFilterChanged(_("Cannot determine sync-direction: Changed filter settings!")) {} void execute(HierarchyObject& hierObj) const { //files std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(), *this); //directories std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), *this); } private: template friend Function std::for_each(Iterator, Iterator, Function); void operator()(FileMapping& fileObj) const { const CompareFilesResult cat = fileObj.getCategory(); if (cat == FILE_LEFT_SIDE_ONLY) fileObj.setSyncDir(SYNC_DIR_RIGHT); else if (cat == FILE_RIGHT_SIDE_ONLY) fileObj.setSyncDir(SYNC_DIR_LEFT); else fileObj.setSyncDirConflict(txtFilterChanged); //set syncDir = SYNC_DIR_INT_CONFLICT } void operator()(DirMapping& dirObj) const { switch (dirObj.getDirCategory()) { case DIR_LEFT_SIDE_ONLY: dirObj.setSyncDir(SYNC_DIR_RIGHT); break; case DIR_RIGHT_SIDE_ONLY: dirObj.setSyncDir(SYNC_DIR_LEFT); break; case DIR_EQUAL: break; } execute(dirObj); //recursion } const wxString txtFilterChanged; }; //---------------------------------------------------------------------------------------------- class RedetermineAuto { public: RedetermineAuto(BaseDirMapping& baseDirectory, DeterminationProblem* handler) : txtBothSidesChanged(_("Both sides have changed since last synchronization!")), txtNoSideChanged(_("Cannot determine sync-direction: No change since last synchronization!")), txtFilterChanged(_("Cannot determine sync-direction: Changed filter settings!")), handler_(handler) { if (baseDirectory.subFiles.empty() && baseDirectory.subDirs.empty()) //don't show nag-screens in this case return; //try to load sync-database files std::pair dirInfo = loadDBFile(baseDirectory); if ( dirInfo.first.get() == NULL || dirInfo.second.get() == NULL) { //use standard settings: SyncConfiguration defaultSync; defaultSync.setVariant(SyncConfiguration::TWOWAY); Redetermine(defaultSync).execute(baseDirectory); return; } const DirInformation& dirInfoLeft = *dirInfo.first; const DirInformation& dirInfoRight = *dirInfo.second; if ( respectFiltering(baseDirectory, dirInfoLeft) && respectFiltering(baseDirectory, dirInfoRight)) { execute(baseDirectory, DetectChanges(&dirInfoLeft.baseDirContainer, FilterProcess(dirInfoLeft.includeFilter, dirInfoLeft.excludeFilter)), DetectChanges(&dirInfoRight.baseDirContainer, FilterProcess(dirInfoRight.includeFilter, dirInfoRight.excludeFilter))); } else if ( !respectFiltering(baseDirectory, dirInfoLeft) && respectFiltering( baseDirectory, dirInfoRight)) { execute(baseDirectory, DetectChanges(&dirInfoLeft.baseDirContainer), DetectChanges(&dirInfoRight.baseDirContainer, FilterProcess(dirInfoRight.includeFilter, dirInfoRight.excludeFilter))); } else if ( respectFiltering( baseDirectory, dirInfoLeft) && !respectFiltering(baseDirectory, dirInfoRight)) { execute(baseDirectory, DetectChanges(&dirInfoLeft.baseDirContainer, FilterProcess(dirInfoLeft.includeFilter, dirInfoLeft.excludeFilter)), DetectChanges(&dirInfoRight.baseDirContainer)); } else { execute(baseDirectory, DetectChanges(&dirInfoLeft.baseDirContainer), DetectChanges(&dirInfoRight.baseDirContainer)); } } private: static bool respectFiltering(const BaseDirMapping& baseDirectory, const DirInformation& dirInfo) { return dirInfo.filterActive && //respect filtering if sync-DB filter is active && different from baseDir's filter (!baseDirectory.getFilter().filterActive || FilterProcess(baseDirectory.getFilter().includeFilter, baseDirectory.getFilter().excludeFilter) != FilterProcess(dirInfo.includeFilter, dirInfo.excludeFilter)); } std::pair loadDBFile(const BaseDirMapping& baseDirectory) //return NULL on failure { try { return loadFromDisk(baseDirectory); } catch (FileError& error) //e.g. incompatible database version { if (handler_) handler_->reportWarning(error.show() + wxT(" \n\n") + _("Using default synchronization directions. Please recheck.")); } return std::pair(); //NULL } template friend Function std::for_each(Iterator, Iterator, Function); template void execute(HierarchyObject& hierObj, const DetectChanges& dbLeft, const DetectChanges& dbRight) { //process files std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(), boost::bind(&RedetermineAuto::processFile, this, _1, boost::ref(dbLeft), boost::ref(dbRight))); //process directories std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), boost::bind(&RedetermineAuto::processDir, this, _1, boost::ref(dbLeft), boost::ref(dbRight))); } template void processFile(FileMapping& fileObj, const DetectChanges& dbLeft, const DetectChanges& dbRight) { const CompareFilesResult cat = fileObj.getCategory(); if (cat == FILE_EQUAL) return; const Answer statusLeft = dbLeft. template detectFileChange(fileObj); const Answer statusRight = dbRight.template detectFileChange(fileObj); if ( statusLeft == CANT_SAY_FILTERING_CHANGED || statusRight == CANT_SAY_FILTERING_CHANGED) { if (cat == FILE_LEFT_SIDE_ONLY) fileObj.setSyncDir(SYNC_DIR_RIGHT); else if (cat == FILE_RIGHT_SIDE_ONLY) fileObj.setSyncDir(SYNC_DIR_LEFT); else fileObj.setSyncDirConflict(txtFilterChanged); //set syncDir = SYNC_DIR_INT_CONFLICT return; } if ( statusLeft == CHANGE_DETECTED && statusRight == NO_CHANGE) fileObj.setSyncDir(SYNC_DIR_RIGHT); else if ( statusLeft == NO_CHANGE && statusRight == CHANGE_DETECTED) fileObj.setSyncDir(SYNC_DIR_LEFT); else if ( statusLeft == CHANGE_DETECTED && statusRight == CHANGE_DETECTED) fileObj.setSyncDirConflict(txtBothSidesChanged); //set syncDir = SYNC_DIR_INT_CONFLICT else if ( statusLeft == NO_CHANGE && statusRight == NO_CHANGE) { if (cat == FILE_LEFT_SIDE_ONLY) fileObj.setSyncDir(SYNC_DIR_RIGHT); else if (cat == FILE_RIGHT_SIDE_ONLY) fileObj.setSyncDir(SYNC_DIR_LEFT); else fileObj.setSyncDirConflict(txtNoSideChanged); //set syncDir = SYNC_DIR_INT_CONFLICT } } template void processDir(DirMapping& dirObj, const DetectChanges& dbLeft, const DetectChanges& dbRight) { const typename DetectChanges ::DirAnswer statusLeft = dbLeft. template detectDirChange(dirObj); const typename DetectChanges::DirAnswer statusRight = dbRight.template detectDirChange(dirObj); const CompareDirResult cat = dirObj.getDirCategory(); if (cat != DIR_EQUAL) { if ( (statusLeft.dirStatus == CANT_SAY_FILTERING_CHANGED || statusRight.dirStatus == CANT_SAY_FILTERING_CHANGED)) { switch (cat) { case DIR_LEFT_SIDE_ONLY: dirObj.setSyncDir(SYNC_DIR_RIGHT); break; case DIR_RIGHT_SIDE_ONLY: dirObj.setSyncDir(SYNC_DIR_LEFT); break; case DIR_EQUAL: assert(false); } SetDirChangedFilter().execute(dirObj); //filter issue for this directory => treat subfiles/-dirs the same return; } else if ( statusLeft.dirStatus == CHANGE_DETECTED && statusRight.dirStatus == NO_CHANGE) dirObj.setSyncDir(SYNC_DIR_RIGHT); else if ( statusLeft.dirStatus == NO_CHANGE && statusRight.dirStatus == CHANGE_DETECTED) dirObj.setSyncDir(SYNC_DIR_LEFT); else if ( statusLeft.dirStatus == CHANGE_DETECTED && statusRight.dirStatus == CHANGE_DETECTED) dirObj.setSyncDirConflict(txtBothSidesChanged); //set syncDir = SYNC_DIR_INT_CONFLICT else if ( statusLeft.dirStatus == NO_CHANGE && statusRight.dirStatus == NO_CHANGE) { switch (cat) { case DIR_LEFT_SIDE_ONLY: dirObj.setSyncDir(SYNC_DIR_RIGHT); break; case DIR_RIGHT_SIDE_ONLY: dirObj.setSyncDir(SYNC_DIR_LEFT); break; case DIR_EQUAL: assert(false); } } } execute(dirObj, statusLeft.subDirInstance, statusRight.subDirInstance); //recursion } const wxString txtBothSidesChanged; const wxString txtNoSideChanged; const wxString txtFilterChanged; DeterminationProblem* const handler_; }; //--------------------------------------------------------------------------------------------------------------- void FreeFileSync::redetermineSyncDirection(const SyncConfiguration& config, BaseDirMapping& baseDirectory, DeterminationProblem* handler) { if (config.automatic) RedetermineAuto(baseDirectory, handler); else Redetermine(config).execute(baseDirectory); } void FreeFileSync::redetermineSyncDirection(const MainConfiguration& currentMainCfg, FolderComparison& folderCmp, DeterminationProblem* handler) { if (folderCmp.size() == 0) return; else if (folderCmp.size() != currentMainCfg.additionalPairs.size() + 1) throw std::logic_error("Programming Error: Contract violation!"); //main pair redetermineSyncDirection(currentMainCfg.syncConfiguration, folderCmp[0], handler); //add. folder pairs for (std::vector::const_iterator i = currentMainCfg.additionalPairs.begin(); i != currentMainCfg.additionalPairs.end(); ++i) { redetermineSyncDirection(i->altSyncConfig.get() ? i->altSyncConfig->syncConfiguration : currentMainCfg.syncConfiguration, folderCmp[i - currentMainCfg.additionalPairs.begin() + 1], handler); } } //--------------------------------------------------------------------------------------------------------------- class SetNewDirection { public: SetNewDirection(SyncDirection newDirection) : newDirection_(newDirection) {} void execute(HierarchyObject& hierObj) const { //directories std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), *this); //files std::for_each(hierObj.subFiles.begin(), hierObj.subFiles.end(), *this); } private: template friend Function std::for_each(Iterator, Iterator, Function); void operator()(FileMapping& fileObj) const { fileObj.setSyncDir(newDirection_); } void operator()(DirMapping& dirObj) const { dirObj.setSyncDir(newDirection_); execute(dirObj); //recursion } const SyncDirection newDirection_; }; void FreeFileSync::setSyncDirectionRec(SyncDirection newDirection, FileSystemObject& fsObj) { fsObj.setSyncDir(newDirection); DirMapping* dirObj = dynamic_cast(&fsObj); if (dirObj) //process subdirectories also! SetNewDirection(newDirection).execute(*dirObj); } void FreeFileSync::applyFiltering(const MainConfiguration& currentMainCfg, FolderComparison& folderCmp) { if (folderCmp.size() == 0) return; else if (folderCmp.size() != currentMainCfg.additionalPairs.size() + 1) throw std::logic_error("Programming Error: Contract violation!"); //main pair FreeFileSync::FilterProcess(currentMainCfg.includeFilter, currentMainCfg.excludeFilter).filterAll(folderCmp[0]); //add. folder pairs for (std::vector::const_iterator i = currentMainCfg.additionalPairs.begin(); i != currentMainCfg.additionalPairs.end(); ++i) { HierarchyObject& baseDirectory = folderCmp[i - currentMainCfg.additionalPairs.begin() + 1]; if (i->altFilter.get()) FreeFileSync::FilterProcess(i->altFilter->includeFilter, i->altFilter->excludeFilter).filterAll(baseDirectory); else FreeFileSync::FilterProcess(currentMainCfg.includeFilter, currentMainCfg.excludeFilter).filterAll(baseDirectory); } } //############################################################################################################ std::pair FreeFileSync::deleteFromGridAndHDPreview( //assemble message containing all files to be deleted const std::vector& rowsToDeleteOnLeft, const std::vector& rowsToDeleteOnRight, const bool deleteOnBothSides) { wxString filesToDelete; int totalDelCount = 0; if (deleteOnBothSides) { //mix selected rows from left and right std::set rowsToDelete(rowsToDeleteOnLeft.begin(), rowsToDeleteOnLeft.end()); rowsToDelete.insert(rowsToDeleteOnRight.begin(), rowsToDeleteOnRight.end()); for (std::set::const_iterator i = rowsToDelete.begin(); i != rowsToDelete.end(); ++i) { const FileSystemObject& currObj = *(*i); if (!currObj.isEmpty()) { filesToDelete += zToWx(currObj.getFullName()) + wxT("\n"); ++totalDelCount; } if (!currObj.isEmpty()) { filesToDelete += zToWx(currObj.getFullName()) + wxT("\n"); ++totalDelCount; } filesToDelete += wxT("\n"); } } else //delete selected files only { for (std::vector::const_iterator i = rowsToDeleteOnLeft.begin(); i != rowsToDeleteOnLeft.end(); ++i) { const FileSystemObject& currObj = *(*i); if (!currObj.isEmpty()) { filesToDelete += zToWx(currObj.getFullName()) + wxT("\n"); ++totalDelCount; } } for (std::vector::const_iterator i = rowsToDeleteOnRight.begin(); i != rowsToDeleteOnRight.end(); ++i) { const FileSystemObject& currObj = *(*i); if (!currObj.isEmpty()) { filesToDelete += zToWx(currObj.getFullName()) + wxT("\n"); ++totalDelCount; } } } return std::make_pair(filesToDelete, totalDelCount); } template void deleteFromGridAndHDOneSide(std::vector& rowsToDeleteOneSide, const bool useRecycleBin, DeleteFilesHandler* statusHandler) { for (std::vector::const_iterator i = rowsToDeleteOneSide.begin(); i != rowsToDeleteOneSide.end(); ++i) { if (!(*i)->isEmpty()) { while (true) { try { FileMapping* fileObj = dynamic_cast(*i); if (fileObj != NULL) { FreeFileSync::removeFile(fileObj->getFullName(), useRecycleBin); fileObj->removeObject(); statusHandler->deletionSuccessful(); //notify successful file/folder deletion } else { DirMapping* dirObj = dynamic_cast(*i); if (dirObj != NULL) { FreeFileSync::removeDirectory(dirObj->getFullName(), useRecycleBin); dirObj->removeObject(); //directory: removes recursively! statusHandler->deletionSuccessful(); //notify successful file/folder deletion } else assert(!"It's no file, no dir, what is it then?"); } break; } catch (const FileError& error) { DeleteFilesHandler::Response rv = statusHandler->reportError(error.show()); if (rv == DeleteFilesHandler::IGNORE_ERROR) break; else if (rv == DeleteFilesHandler::RETRY) ; //continue in loop else assert (false); } } } } } class FinalizeDeletion { public: FinalizeDeletion(FolderComparison& folderCmp, const MainConfiguration& mainConfig) : folderCmp_(folderCmp), mainConfig_(mainConfig) {} ~FinalizeDeletion() { std::for_each(folderCmp_.begin(), folderCmp_.end(), FileSystemObject::removeEmpty); redetermineSyncDirection(mainConfig_, folderCmp_, NULL); } private: FolderComparison& folderCmp_; const MainConfiguration& mainConfig_; }; void FreeFileSync::deleteFromGridAndHD(FolderComparison& folderCmp, //attention: rows will be physically deleted! std::vector& rowsToDeleteOnLeft, //refresh GUI grid after deletion to remove invalid rows std::vector& rowsToDeleteOnRight, //all pointers need to be bound! const bool deleteOnBothSides, const bool useRecycleBin, const MainConfiguration& mainConfig, DeleteFilesHandler* statusHandler) { if (folderCmp.size() == 0) return; else if (folderCmp.size() != mainConfig.additionalPairs.size() + 1) throw std::logic_error("Programming Error: Contract violation!"); FinalizeDeletion dummy(folderCmp, mainConfig); //ensure cleanup: redetermination of sync-directions and removal of invalid rows if (deleteOnBothSides) { //mix selected rows from left and right (and remove duplicates) std::set temp(rowsToDeleteOnLeft.begin(), rowsToDeleteOnLeft.end()); temp.insert(rowsToDeleteOnRight.begin(), rowsToDeleteOnRight.end()); std::vector rowsToDeleteBothSides(temp.begin(), temp.end()); deleteFromGridAndHDOneSide(rowsToDeleteBothSides, useRecycleBin, statusHandler); deleteFromGridAndHDOneSide(rowsToDeleteBothSides, useRecycleBin, statusHandler); } else { deleteFromGridAndHDOneSide(rowsToDeleteOnLeft, useRecycleBin, statusHandler); deleteFromGridAndHDOneSide(rowsToDeleteOnRight, useRecycleBin, statusHandler); } } //############################################################################################################ /*Statistical theory: detect daylight saving time (DST) switch by comparing files that exist on both sides (and have same filesizes). If there are "enough" that have a shift by +-1h then assert that DST switch occured. What is "enough" =: N? N should be large enough AND small enough that the following two errors remain small: Error 1: A DST switch is detected although there was none Error 2: A DST switch is not detected although it took place Error 1 results in lower bound, error 2 in upper bound for N. Target: Choose N such that probability of error 1 and error 2 is lower than 0.001 (0.1%) Definitions: p1: probability that a file with same filesize on both sides was changed nevertheless p2: probability that a changed file has +1h shift in filetime due to a change M: number of files with same filesize on both sides in total N: number of files with same filesize and time-diff +1h when DST check shall detect "true" X: number of files with same filesize that have a +1h difference after change Error 1 ("many files have +1h shift by chance") imposes: Probability of error 1: (binomial distribution) P(X >= N) = 1 - P(X <= N - 1) = 1 - sum_i=0^N-1 p3^i * (1 - p3)^(M - i) (M above i) shall be <= 0.0005 with p3 := p1 * p2 Probability of error 2 also will be <= 0.0005 if we choose N as lowest number that satisfies the preceding formula. Proof is left to the reader. The function M |-> N behaves almost linearly and can be empirically approximated by: N(M) = 2 for 0 <= M <= 500 125/1000000 * M + 5 for 500 < M <= 50000 77/1000000 * M + 10 for 50000 < M <= 400000 60/1000000 * M + 35 for 400000 < M inline bool sameFileTime(const time_t a, const time_t b) { if (a < b) return b - a <= FILE_TIME_PRECISION; else return a - b <= FILE_TIME_PRECISION; } #ifdef FFS_WIN unsigned int getThreshold(const unsigned filesWithSameSizeTotal) { if (filesWithSameSizeTotal <= 500) return 2; else if (filesWithSameSizeTotal <= 50000) return unsigned(125.0/1000000 * filesWithSameSizeTotal + 5.0); else if (filesWithSameSizeTotal <= 400000) return unsigned(77.0/1000000 * filesWithSameSizeTotal + 10.0); else return unsigned(60.0/1000000 * filesWithSameSizeTotal + 35.0); } void FreeFileSync::checkForDSTChange(const FileCompareResult& gridData, const std::vector& directoryPairsFormatted, int& timeShift, wxString& driveName) { driveName.Clear(); timeShift = 0; TIME_ZONE_INFORMATION dummy; DWORD rv = GetTimeZoneInformation(&dummy); if (rv == TIME_ZONE_ID_UNKNOWN) return; bool dstActive = rv == TIME_ZONE_ID_DAYLIGHT; for (std::vector::const_iterator i = directoryPairsFormatted.begin(); i != directoryPairsFormatted.end(); ++i) { bool leftDirIsFat = isFatDrive(i->leftDirectory); bool rightDirIsFat = isFatDrive(i->rightDirectory); if (leftDirIsFat || rightDirIsFat) { unsigned int filesTotal = 0; //total number of files (with same size on both sides) unsigned int plusOneHourCount = 0; //number of files with +1h time shift unsigned int minusOneHourCount = 0; // " for (FileCompareResult::const_iterator j = gridData.begin(); j != gridData.end(); ++j) { const FileDescrLine& leftFile = j->fileDescrLeft; const FileDescrLine& rightFile = j->fileDescrRight; if ( leftFile.objType == FileDescrLine::TYPE_FILE && rightFile.objType == FileDescrLine::TYPE_FILE && leftFile.fileSize == rightFile.fileSize && leftFile.directory.CmpNoCase(i->leftDirectory.c_str()) == 0 && //Windows does NOT distinguish between upper/lower-case rightFile.directory.CmpNoCase(i->rightDirectory.c_str()) == 0) // { ++filesTotal; if (sameFileTime(leftFile.lastWriteTimeRaw - 3600, rightFile.lastWriteTimeRaw)) ++plusOneHourCount; else if (sameFileTime(leftFile.lastWriteTimeRaw + 3600, rightFile.lastWriteTimeRaw)) ++minusOneHourCount; } } unsigned int threshold = getThreshold(filesTotal); if (plusOneHourCount >= threshold) { if (dstActive) { if (rightDirIsFat) //it should be FAT; else this were some kind of error { timeShift = 3600; driveName = getDriveName(i->rightDirectory); } } else { if (leftDirIsFat) //it should be FAT; else this were some kind of error { timeShift = -3600; driveName = getDriveName(i->leftDirectory); } } return; } else if (minusOneHourCount >= threshold) { if (dstActive) { if (leftDirIsFat) //it should be FAT; else this were some kind of error { timeShift = 3600; driveName = getDriveName(i->leftDirectory); } } else { if (rightDirIsFat) //it should be FAT; else this were some kind of error { timeShift = -3600; driveName = getDriveName(i->rightDirectory); } } return; } } } } #endif //FFS_WIN */