summaryrefslogtreecommitdiff
path: root/algorithm.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:24:35 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:24:35 +0200
commit460091fb0b2ff114cc741372f15bb43b702ea3b1 (patch)
tree0562c2eda4c66969c6e6d0910080db9f5b0def3e /algorithm.cpp
parent5.15 (diff)
downloadFreeFileSync-460091fb0b2ff114cc741372f15bb43b702ea3b1.tar.gz
FreeFileSync-460091fb0b2ff114cc741372f15bb43b702ea3b1.tar.bz2
FreeFileSync-460091fb0b2ff114cc741372f15bb43b702ea3b1.zip
5.16
Diffstat (limited to 'algorithm.cpp')
-rw-r--r--algorithm.cpp188
1 files changed, 81 insertions, 107 deletions
diff --git a/algorithm.cpp b/algorithm.cpp
index 46239369..0d591206 100644
--- a/algorithm.cpp
+++ b/algorithm.cpp
@@ -7,7 +7,6 @@
#include "algorithm.h"
#include <set>
#include <stdexcept>
-#include <tuple>
#include <zen/file_handling.h>
#include <zen/recycler.h>
#include <zen/stl_tools.h>
@@ -225,14 +224,15 @@ bool stillInSync(const InSyncFile& dbFile, CompareVariant compareVar, size_t fil
switch (compareVar)
{
case CMP_BY_TIME_SIZE:
- return dbFile.inSyncType == InSyncFile::IN_SYNC_BINARY_EQUAL || //special rule: this is already "good enough" for CMP_BY_TIME_SIZE!
- //case-sensitive short name match is a database invariant!
- (CmpFileTime::getResult(dbFile.left.lastWriteTimeRaw, dbFile.right.lastWriteTimeRaw, fileTimeTolerance) == CmpFileTime::TIME_EQUAL &&
- dbFile.left.fileSize == dbFile.right.fileSize);
+ if (dbFile.inSyncType == IN_SYNC_BINARY_EQUAL) return true; //special rule: this is already "good enough" for CMP_BY_TIME_SIZE!
+
+ return //case-sensitive short name match is a database invariant!
+ CmpFileTime::getResult(dbFile.left.lastWriteTimeRaw, dbFile.right.lastWriteTimeRaw, fileTimeTolerance) == CmpFileTime::TIME_EQUAL &&
+ dbFile.left.fileSize == dbFile.right.fileSize;
case CMP_BY_CONTENT:
//case-sensitive short name match is a database invariant!
- return dbFile.inSyncType == InSyncFile::IN_SYNC_BINARY_EQUAL;
+ return dbFile.inSyncType == IN_SYNC_BINARY_EQUAL;
//in contrast to comparison, we don't care about modification time here!
}
assert(false);
@@ -262,26 +262,29 @@ bool isEqual(const SymLinkMapping& linkObj, const InSyncDir::LinkList::value_typ
return linkObj.getShortName<side>() == shortNameDb &&
//respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes its modification date by up to 2 seconds
- sameFileTime(linkObj.getLastWriteTime<side>(), descrDb.lastWriteTimeRaw, 2) &&
-#ifdef FFS_WIN //comparison of symbolic link type is relevant for Windows only
- linkObj.getLinkType<side>() == descrDb.type &&
-#endif
- linkObj.getTargetPath<side>() == descrDb.targetPath;
+ sameFileTime(linkObj.getLastWriteTime<side>(), descrDb.lastWriteTimeRaw, 2);
}
//check whether database entry is in sync considering *current* comparison settings
inline
-bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar)
+bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar, size_t fileTimeTolerance)
{
- return !dbLink.left .targetPath.empty() && //if one of these is empty, we can't make a statement whether both sides are in sync
- !dbLink.right.targetPath.empty() && //
-#ifdef FFS_WIN //type of symbolic link is relevant for Windows only
- dbLink.left.type == dbLink.right.type &&
-#endif
- dbLink.left.targetPath == dbLink.right.targetPath;
- //case-sensitive short name match is a database invariant!
- //in contrast to comparison, we don't care about modification time!
+ switch (compareVar)
+ {
+ case CMP_BY_TIME_SIZE:
+ if (dbLink.inSyncType == IN_SYNC_BINARY_EQUAL) return true; //special rule: this is already "good enough" for CMP_BY_TIME_SIZE!
+
+ return //case-sensitive short name match is a database invariant!
+ CmpFileTime::getResult(dbLink.left.lastWriteTimeRaw, dbLink.right.lastWriteTimeRaw, fileTimeTolerance) == CmpFileTime::TIME_EQUAL;
+
+ case CMP_BY_CONTENT:
+ //case-sensitive short name match is a database invariant!
+ return dbLink.inSyncType == IN_SYNC_BINARY_EQUAL;
+ //in contrast to comparison, we don't care about modification time here!
+ }
+ assert(false);
+ return false;
}
//--------------------------------------------------------------------
@@ -349,10 +352,7 @@ private:
//----------- detect renamed files -----------------
if (!exLeftOnly.empty() && !exRightOnly.empty())
- {
- collectEqualDbEntries(*lastSyncState); //fill map "onceEqual"
- detectRenamedFiles();
- }
+ detectRenamedFiles(*lastSyncState);
}
std::shared_ptr<InSyncDir> loadDBFile(const BaseDirMapping& baseMap) //return nullptr on failure
@@ -387,12 +387,22 @@ private:
if (cat == FILE_LEFT_SIDE_ONLY)
{
if (fileObj.getFileId<LEFT_SIDE>() != FileId())
- exLeftOnly.push_back(&fileObj);
+ {
+ auto rv = exLeftOnly.insert(std::make_pair(fileObj.getFileId<LEFT_SIDE>(), &fileObj));
+ assert(rv.second);
+ if (!rv.second) //duplicate file ID!
+ rv.first->second = nullptr;
+ }
}
else if (cat == FILE_RIGHT_SIDE_ONLY)
{
if (fileObj.getFileId<RIGHT_SIDE>() != FileId())
- exRightOnly.insert(std::make_pair(getFileIdKey<RIGHT_SIDE>(fileObj), &fileObj));
+ {
+ auto rv = exRightOnly.insert(std::make_pair(fileObj.getFileId<RIGHT_SIDE>(), &fileObj));
+ assert(rv.second);
+ if (!rv.second) //duplicate file ID!
+ rv.first->second = nullptr;
+ }
}
//----------------------------------------------------------------------
@@ -455,7 +465,7 @@ private:
if (changeOnLeft != changeOnRight)
{
//if database entry not in sync according to current settings! -> do not set direction based on async status!
- if (dbEntry && !stillInSync(dbEntry->second, cmpVar))
+ if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance))
linkObj.setSyncDirConflict(txtDbNotInSync);
else
linkObj.setSyncDir(changeOnLeft ? SYNC_DIR_RIGHT : SYNC_DIR_LEFT);
@@ -515,63 +525,51 @@ private:
recurse(dirObj, dbEntry ? &dbEntry->second : nullptr); //recursion
}
- void collectEqualDbEntries(const InSyncDir& container)
+ //note: - we cannot integrate this traversal into "recurse()" since it may take a *slightly* different path: e.g. file renamed on both sides
+ void detectRenamedFiles(InSyncDir& container)
{
- //note: we cannot integrate this traversal into "recurse()" since it may take a *slightly* different path: e.g. file renamed on both sides
-
std::for_each(container.files.begin(), container.files.end(),
- [&](const std::pair<Zstring, InSyncFile>& filePair)
- {
- if (getFileId(filePair.second.left ) != FileId() &&
- getFileId(filePair.second.right) != FileId() &&
- stillInSync(filePair.second, cmpVar, fileTimeTolerance))
- onceEqual.insert(std::make_pair(getFileIdKey(filePair.second.left), getFileIdKey(filePair.second.right)));
- });
+ [&](std::pair<const Zstring, InSyncFile>& filePair) { findAndSetMovePair(filePair.second); });
std::for_each(container.dirs.begin(), container.dirs.end(),
- [&](const std::pair<Zstring, InSyncDir>& dirPair) { collectEqualDbEntries(dirPair.second); });
+ [&](std::pair<const Zstring, InSyncDir>& dirPair) { detectRenamedFiles(dirPair.second); });
}
- typedef std::tuple<Int64, UInt64, FileId> FileIdKey; //(date, size, file ID)
-
-
- //modification date is *not* considered as part of container key, so check here!
- template <class Container>
- static typename Container::const_iterator findValue(const Container& cnt, const FileIdKey& key)
+ template <SelectedSide side>
+ static bool sameSizeAndDate(const FileMapping& fsObj, const FileDescriptor& fileDescr)
{
- auto iterPair = cnt.equal_range(key); //since file id is already unique, we expect a single-element range at most
- auto it = std::find_if(iterPair.first, iterPair.second,
- [&](const typename Container::value_type& item)
- {
- return sameFileTime(std::get<0>(item.first), std::get<0>(key), 2); //respect 2 second FAT/FAT32 precision!
- //the file time could be inferred from the source side after a file copy while a slightly different time is stored on a FAT32 disk!
- });
- return it == iterPair.second ? cnt.end() : it;
+ return fsObj.getFileSize<side>() == fileDescr.fileSize &&
+ sameFileTime(fsObj.getLastWriteTime<side>(), fileDescr.lastWriteTimeRaw, 2); //respect 2 second FAT/FAT32 precision!
+ //PS: *never* allow 2 sec tolerance as container predicate!!
+ // => no strict weak ordering relation! reason: no transitivity of equivalence!
}
- void detectRenamedFiles() const
+ void findAndSetMovePair(const InSyncFile& dbEntry) const
{
- std::for_each(exLeftOnly.begin(), exLeftOnly.end(),
- [&](FileMapping* fileLeftOnly)
- {
- const FileIdKey& keyLeft = RedetermineAuto::getFileIdKey<LEFT_SIDE>(*fileLeftOnly);
-
- auto it = findValue(onceEqual, keyLeft);
- if (it != onceEqual.end())
- {
- const FileIdKey& keyRight = it->second;
-
- auto iter2 = findValue(exRightOnly, keyRight);
- if (iter2 != exRightOnly.end())
- {
- FileMapping* fileRightOnly = iter2->second;
+ const FileId idLeft = getFileId(dbEntry.left);
+ const FileId idRight = getFileId(dbEntry.right);
- //found a pair, mark it!
- fileLeftOnly ->setMoveRef(fileRightOnly->getId());
- fileRightOnly->setMoveRef(fileLeftOnly ->getId());
- }
- }
- });
+ if (idLeft != FileId() &&
+ idRight != FileId() &&
+ stillInSync(dbEntry, cmpVar, fileTimeTolerance))
+ {
+ auto itL = exLeftOnly.find(idLeft);
+ if (itL != exLeftOnly.end())
+ if (FileMapping* fileLeftOnly = itL->second) //= nullptr, if duplicate ID!
+ if (sameSizeAndDate<LEFT_SIDE>(*fileLeftOnly, dbEntry.left))
+ {
+ auto itR = exRightOnly.find(idRight);
+ if (itR != exRightOnly.end())
+ if (FileMapping* fileRightOnly = itR->second) //= nullptr, if duplicate ID!
+ if (sameSizeAndDate<RIGHT_SIDE>(*fileRightOnly, dbEntry.right))
+ if (fileLeftOnly ->getMoveRef() == nullptr && //the db may contain duplicate file ids on left or right side: e.g. consider aliasing through symlinks
+ fileRightOnly->getMoveRef() == nullptr) //=> should not be a problem (same id, size, date => alias!) but don't let a row participate in two move pairs!
+ {
+ fileLeftOnly ->setMoveRef(fileRightOnly->getId()); //found a pair, mark it!
+ fileRightOnly->setMoveRef(fileLeftOnly ->getId()); //
+ }
+ }
+ }
}
const std::wstring txtBothSidesChanged;
@@ -580,36 +578,10 @@ private:
const CompareVariant cmpVar;
const size_t fileTimeTolerance;
-
std::function<void(const std::wstring&)> reportWarning_;
- //detection of renamed files
- template <SelectedSide side>
- static FileIdKey getFileIdKey(const FileMapping& fsObj) { return std::make_tuple(fsObj.getLastWriteTime<side>(), fsObj.getFileSize<side>(), fsObj.getFileId<side>()); }
- static FileIdKey getFileIdKey(const FileDescriptor& fileDescr) { return std::make_tuple(fileDescr.lastWriteTimeRaw, fileDescr.fileSize, getFileId(fileDescr)); }
-
- struct LessFileIdKey
- {
- bool operator()(const FileIdKey& lhs, const FileIdKey& rhs) const
- {
- //caveat: *don't* allow 2 sec tolerance as container predicate!!
- // => no strict weak ordering relation! reason: no transitivity of equivalence!
-
- //-> bad: if (!sameFileTime(std::get<0>(lhs), std::get<0>(rhs), 2))
- // return std::get<0>(lhs) < std::get<0>(rhs);
-
- if (std::get<1>(lhs) != std::get<1>(rhs)) //file size
- return std::get<1>(lhs) < std::get<1>(rhs);
-
- return std::get<2>(lhs) < std::get<2>(rhs); //file id
- }
- };
-
- std::vector<FileMapping*> exLeftOnly;
-
- std::multimap<FileIdKey, FileIdKey, LessFileIdKey> onceEqual; //associates left and right database entries which are considered "equal" := "same name, size, date"
-
- std::multimap<FileIdKey, FileMapping*, LessFileIdKey> exRightOnly;
+ std::map<FileId, FileMapping*> exLeftOnly; //FileMapping* == nullptr for duplicate ids! => consider aliasing through symlinks!
+ std::map<FileId, FileMapping*> exRightOnly; //=> avoid ambiguity for mixtures of files/symlinks on one side and allow 1-1 mapping only!
/*
detect renamed files
@@ -623,10 +595,10 @@ private:
Algorithm:
----------
- DB-file left --- (name, size, date) ---> DB-file right
- /|\ |
+ DB-file left <--- (name, size, date) ---> DB-file right
+ | |
| (file ID, size, date) | (file ID, size, date)
- | \|/
+ \|/ \|/
file left only file right only
FAT caveat: File Ids are generally not stable when file is either moved or renamed!
@@ -1289,12 +1261,14 @@ struct ItemDeleter : public FSObjectVisitor //throw FileError, but nothrow cons
if (useRecycleBin_)
zen::recycleOrDelete(linkObj.getFullName<side>()); //throw FileError
else
- switch (linkObj.getLinkType<side>())
+ switch (getSymlinkType(linkObj.getFullName<side>()))
{
- case LinkDescriptor::TYPE_DIR:
+ case SYMLINK_TYPE_DIR:
zen::removeDirectory(linkObj.getFullName<side>()); //throw FileError
break;
- case LinkDescriptor::TYPE_FILE:
+
+ case SYMLINK_TYPE_FILE:
+ case SYMLINK_TYPE_UNKNOWN:
zen::removeFile(linkObj.getFullName<side>()); //throw FileError
break;
}
bgstack15