summaryrefslogtreecommitdiff
path: root/algorithm.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:20:29 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:20:29 +0200
commitb8f13e45be884dc12884ebe8f3dcd9eecb23a106 (patch)
tree22a6d8b96815d626061ff3e2d432c13078fca5c4 /algorithm.cpp
parent5.4 (diff)
downloadFreeFileSync-b8f13e45be884dc12884ebe8f3dcd9eecb23a106.tar.gz
FreeFileSync-b8f13e45be884dc12884ebe8f3dcd9eecb23a106.tar.bz2
FreeFileSync-b8f13e45be884dc12884ebe8f3dcd9eecb23a106.zip
5.5
Diffstat (limited to 'algorithm.cpp')
-rw-r--r--algorithm.cpp637
1 files changed, 213 insertions, 424 deletions
diff --git a/algorithm.cpp b/algorithm.cpp
index 771fcaea..6de28baa 100644
--- a/algorithm.cpp
+++ b/algorithm.cpp
@@ -14,7 +14,6 @@
#include <zen/recycler.h>
#include <wx/msgdlg.h>
#include "lib/norm_filter.h"
-#include <wx+/string_conv.h>
#include "lib/db_file.h"
#include <zen/scope_guard.h>
#include "lib/cmp_filetime.h"
@@ -31,8 +30,10 @@ void zen::swapGrids(const MainConfiguration& config, FolderComparison& folderCmp
redetermineSyncDirection(config, folderCmp, [](const std::wstring&) {});
}
-
//----------------------------------------------------------------------------------------------
+
+namespace
+{
class Redetermine
{
public:
@@ -77,17 +78,15 @@ private:
fileObj.setSyncDir(dirCfg.different);
break;
case FILE_CONFLICT:
+ case FILE_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize"
if (dirCfg.conflict == SYNC_DIR_NONE)
- fileObj.setSyncDirConflict(fileObj.getCatConflict()); //take over category conflict
+ fileObj.setSyncDirConflict(getCategoryDescription(fileObj)); //take over category conflict
else
fileObj.setSyncDir(dirCfg.conflict);
break;
case FILE_EQUAL:
fileObj.setSyncDir(SYNC_DIR_NONE);
break;
- case FILE_DIFFERENT_METADATA:
- fileObj.setSyncDir(dirCfg.conflict); //use setting from "conflict/cannot categorize"
- break;
}
}
@@ -108,8 +107,9 @@ private:
linkObj.setSyncDir(dirCfg.rightNewer);
break;
case SYMLINK_CONFLICT:
+ case SYMLINK_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize"
if (dirCfg.conflict == SYNC_DIR_NONE)
- linkObj.setSyncDirConflict(linkObj.getCatConflict()); //take over category conflict
+ linkObj.setSyncDirConflict(getCategoryDescription(linkObj)); //take over category conflict
else
linkObj.setSyncDir(dirCfg.conflict);
break;
@@ -119,10 +119,6 @@ private:
case SYMLINK_EQUAL:
linkObj.setSyncDir(SYNC_DIR_NONE);
break;
- case SYMLINK_DIFFERENT_METADATA:
- linkObj.setSyncDir(dirCfg.conflict); //use setting from "conflict/cannot categorize"
- break;
-
}
}
@@ -139,8 +135,11 @@ private:
case DIR_EQUAL:
dirObj.setSyncDir(SYNC_DIR_NONE);
break;
- case DIR_DIFFERENT_METADATA:
- dirObj.setSyncDir(dirCfg.conflict); //use setting from "conflict/cannot categorize"
+ case DIR_DIFFERENT_METADATA: //use setting from "conflict/cannot categorize"
+ if (dirCfg.conflict == SYNC_DIR_NONE)
+ dirObj.setSyncDirConflict(getCategoryDescription(dirObj)); //take over category conflict
+ else
+ dirObj.setSyncDir(dirCfg.conflict);
break;
}
@@ -171,210 +170,137 @@ struct AllEqual //test if non-equal items exist in scanned data
}); //directories
}
};
-
+}
bool zen::allElementsEqual(const FolderComparison& folderCmp)
{
return std::all_of(begin(folderCmp), end(folderCmp), AllEqual());
}
-//---------------------------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------------------------
-class DataSetFile
+namespace
{
-public:
- DataSetFile() {}
+template <SelectedSide side> inline
+const FileDescriptor& getDescriptor(const InSyncFile& dbFile) { return dbFile.left; }
- DataSetFile(const Zstring& name, const FileDescriptor& fileDescr)
- {
- shortName = name;
- lastWriteTime = fileDescr.lastWriteTimeRaw;
- fileSize = fileDescr.fileSize;
- }
+template <> inline
+const FileDescriptor& getDescriptor<RIGHT_SIDE>(const InSyncFile& dbFile) { return dbFile.right; }
- DataSetFile(const FileMapping& fileObj, Int2Type<LEFT_SIDE>)
- {
- init<LEFT_SIDE>(fileObj);
- }
- DataSetFile(const FileMapping& fileObj, Int2Type<RIGHT_SIDE>)
- {
- init<RIGHT_SIDE>(fileObj);
- }
+//check whether database entry and current item match: *irrespective* of current comparison settings
+template <SelectedSide side> inline
+bool isEqual(const FileMapping& fileObj, const InSyncDir::FileList::value_type* dbFile)
+{
+ if (fileObj.isEmpty<side>())
+ return !dbFile;
+ else if (!dbFile)
+ return false;
+
+ const Zstring& shortNameDb = dbFile->first;
+ const FileDescriptor& descrDb = getDescriptor<side>(dbFile->second);
+
+ return fileObj.getShortName<side>() == shortNameDb && //detect changes in case (windows)
+ //respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes it's modification date by up to 2 seconds
+ sameFileTime(fileObj.getLastWriteTime<side>(), descrDb.lastWriteTimeRaw, 2) &&
+ fileObj.getFileSize<side>() == descrDb.fileSize;
+ //note: we do *not* consider FileId here, but are only interested in *visual* changes. Consider user moving data to some other medium, this is not a change!
+}
- inline friend
- bool operator==(const DataSetFile& lhs, const DataSetFile& rhs)
- {
- if (lhs.shortName.empty())
- return rhs.shortName.empty();
- else if (rhs.shortName.empty())
- return false;
-
- return lhs.shortName == rhs.shortName && //detect changes in case (windows)
- //respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes it's modification date by up to 2 seconds
- sameFileTime(lhs.lastWriteTime, rhs.lastWriteTime, 2) &&
- lhs.fileSize == rhs.fileSize;
- }
-private:
- template <SelectedSide side>
- void init(const FileMapping& fileObj)
- {
- if (!fileObj.isEmpty<side>())
- {
- shortName = fileObj.getShortName<side>();
- lastWriteTime = fileObj.getLastWriteTime<side>();
- fileSize = fileObj.getFileSize<side>();
- }
+//check whether database entry is in sync considering *current* comparison settings
+inline
+bool stillInSync(const InSyncFile& dbFile, CompareVariant compareVar, size_t fileTimeTolerance)
+{
+ 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);
+
+ case CMP_BY_CONTENT:
+ //case-sensitive short name match is a database invariant!
+ return dbFile.inSyncType == InSyncFile::IN_SYNC_BINARY_EQUAL;
+ //in contrast to comparison, we don't care about modification time here!
}
+ assert(false);
+ return false;
+}
- Zstring shortName; //empty if object not existing
- zen::Int64 lastWriteTime;
- zen::UInt64 fileSize;
-
- //note: we do *not* consider FileId here, but are only interested in *visual* changes. Consider user moving data to some other medium, this is not a change!
-};
+//--------------------------------------------------------------------
+template <SelectedSide side> inline
+const LinkDescriptor& getDescriptor(const InSyncSymlink& dbLink) { return dbLink.left; }
-//--------------------------------------------------------------------
-class DataSetSymlink
-{
-public:
- DataSetSymlink()
-#ifdef FFS_WIN
- : type(LinkDescriptor::TYPE_FILE) //dummy value
-#endif
- {}
+template <> inline
+const LinkDescriptor& getDescriptor<RIGHT_SIDE>(const InSyncSymlink& dbLink) { return dbLink.right; }
- DataSetSymlink(const Zstring& name, const LinkDescriptor& linkDescr)
- {
- shortName = name;
- lastWriteTime = linkDescr.lastWriteTimeRaw;
- targetPath = linkDescr.targetPath;
-#ifdef FFS_WIN //type of symbolic link is relevant for Windows only
- type = linkDescr.type;
-#endif
- }
- DataSetSymlink(const SymLinkMapping& linkObj, Int2Type<LEFT_SIDE>)
- {
- init<LEFT_SIDE>(linkObj);
- }
+//check whether database entry and current item match: *irrespective* of current comparison settings
+template <SelectedSide side> inline
+bool isEqual(const SymLinkMapping& linkObj, const InSyncDir::LinkList::value_type* dbLink)
+{
+ if (linkObj.isEmpty<side>())
+ return !dbLink;
+ else if (!dbLink)
+ return false;
- DataSetSymlink(const SymLinkMapping& linkObj, Int2Type<RIGHT_SIDE>)
- {
- init<RIGHT_SIDE>(linkObj);
- }
+ const Zstring& shortNameDb = dbLink->first;
+ const LinkDescriptor& descrDb = getDescriptor<side>(dbLink->second);
- inline friend
- bool operator==(const DataSetSymlink& lhs, const DataSetSymlink& rhs)
- {
- if (lhs.shortName.empty()) //test if object is existing at all
- return rhs.shortName.empty();
- else if (rhs.shortName.empty())
- return false;
-
- return lhs.shortName == rhs.shortName &&
- //respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes it's modification date by up to 2 seconds
- sameFileTime(lhs.lastWriteTime, rhs.lastWriteTime, 2) &&
+ return linkObj.getShortName<side>() == shortNameDb &&
+ //respect 2 second FAT/FAT32 precision! copying a file to a FAT32 drive changes it's 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
- lhs.type == rhs.type &&
+ linkObj.getLinkType<side>() == descrDb.type &&
#endif
- lhs.targetPath == rhs.targetPath;
- }
+ linkObj.getTargetPath<side>() == descrDb.targetPath;
+}
-private:
- template <SelectedSide side>
- void init(const SymLinkMapping& linkObj)
- {
-#ifdef FFS_WIN
- type = LinkDescriptor::TYPE_FILE; //always initialize
-#endif
- if (!linkObj.isEmpty<side>())
- {
- shortName = linkObj.getShortName<side>();
- lastWriteTime = linkObj.getLastWriteTime<side>();
- targetPath = linkObj.getTargetPath<side>();
+//check whether database entry is in sync considering *current* comparison settings
+inline
+bool stillInSync(const InSyncSymlink& dbLink, CompareVariant compareVar)
+{
+ 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
- type = linkObj.getLinkType<side>();
+ 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!
+}
- Zstring shortName; //empty if object not existing
- zen::Int64 lastWriteTime;
- Zstring targetPath;
-#ifdef FFS_WIN
- LinkDescriptor::LinkType type;
-#endif
-};
//--------------------------------------------------------------------
-
-class DataSetDir
+//check whether database entry and current item match: *irrespective* of current comparison settings
+template <SelectedSide side> inline
+bool isEqual(const DirMapping& dirObj, const InSyncDir::DirList::value_type* dbDir)
{
-public:
- DataSetDir() {}
-
- DataSetDir(const Zstring& name) :
- shortName(name) {}
+ if (dirObj.isEmpty<side>())
+ return !dbDir || dbDir->second.status == InSyncDir::STATUS_STRAW_MAN;
+ else if (!dbDir || dbDir->second.status == InSyncDir::STATUS_STRAW_MAN)
+ return false;
- DataSetDir(const DirMapping& dirObj, Int2Type<LEFT_SIDE>) :
- shortName(dirObj.getShortName<LEFT_SIDE>()) {}
+ const Zstring& shortNameDb = dbDir->first;
- DataSetDir(const DirMapping& dirObj, Int2Type<RIGHT_SIDE>) :
- shortName(dirObj.getShortName<RIGHT_SIDE>()) {}
-
- inline friend
- bool operator==(const DataSetDir& lhs, const DataSetDir& rhs)
- {
- return lhs.shortName == rhs.shortName;
- }
-
-private:
- Zstring shortName; //empty if object not existing
-};
-//--------------------------------------------------------------------------------------------------------
-
-DataSetFile retrieveDataSetFile(const Zstring& objShortName, const DirContainer* dbDirectory)
-{
- if (dbDirectory)
- {
- DirContainer::FileList::const_iterator iter = dbDirectory->files.find(objShortName);
- if (iter != dbDirectory->files.end())
- return DataSetFile(iter->first, iter->second);
- }
-
- return DataSetFile(); //object not found
-}
-
-DataSetSymlink retrieveDataSetSymlink(const Zstring& objShortName, const DirContainer* dbDirectory)
-{
- if (dbDirectory)
- {
- DirContainer::LinkList::const_iterator iter = dbDirectory->links.find(objShortName);
- if (iter != dbDirectory->links.end())
- return DataSetSymlink(iter->first, iter->second);
- }
-
- return DataSetSymlink(); //object not found
+ return dirObj.getShortName<side>() == shortNameDb;
}
-std::pair<DataSetDir, const DirContainer*> retrieveDataSetDir(const Zstring& objShortName, const DirContainer* dbDirectory)
+inline
+bool stillInSync(const InSyncDir& dbDir)
{
- if (dbDirectory)
- {
- DirContainer::DirList::const_iterator iter = dbDirectory->dirs.find(objShortName);
- if (iter != dbDirectory->dirs.end())
- return std::make_pair(DataSetDir(iter->first), &iter->second);
- }
-
- return std::make_pair(DataSetDir(), nullptr); //object not found
+ //case-sensitive short name match is a database invariant!
+ //InSyncDir::STATUS_STRAW_MAN considered
+ return true;
}
//----------------------------------------------------------------------------------------------
+
class RedetermineAuto
{
public:
@@ -387,17 +313,17 @@ private:
RedetermineAuto(BaseDirMapping& baseDirectory, std::function<void(const std::wstring&)> reportWarning) :
txtBothSidesChanged(_("Both sides have changed since last synchronization!")),
txtNoSideChanged(_("Cannot determine sync-direction:") + L" \n" + _("No change since last synchronization!")),
- txtFilterChanged(_("Cannot determine sync-direction:") + L" \n" + _("Filter settings have changed!")),
- txtLastSyncFail (_("Cannot determine sync-direction:") + L" \n" + _("The file was not processed by last synchronization!")),
+ txtDbNotInSync(_("Cannot determine sync-direction:") + L" \n" + _("The corresponding database entries are not in sync considering current settings.")),
+ cmpVar(baseDirectory.getCompVariant()),
+ fileTimeTolerance(baseDirectory.getFileTimeTolerance()),
reportWarning_(reportWarning)
{
if (AllEqual()(baseDirectory)) //nothing to do: abort and don't show any nag-screens
return;
//try to load sync-database files
- std::pair<DirInfoPtr, DirInfoPtr> dirInfo = loadDBFile(baseDirectory);
- if (dirInfo.first.get() == nullptr ||
- dirInfo.second.get() == nullptr)
+ std::shared_ptr<InSyncDir> lastSyncState = loadDBFile(baseDirectory);
+ if (!lastSyncState)
{
//set conservative "two-way" directions
DirectionSet twoWayCfg = getTwoWaySet();
@@ -406,48 +332,24 @@ private:
return;
}
- const DirInformation& dirInfoLeft = *dirInfo.first;
- const DirInformation& dirInfoRight = *dirInfo.second;
-
//-> considering filter not relevant:
//if narrowing filter: all ok; if widening filter (if file ex on both sides -> conflict, fine; if file ex. on one side: copy to other side: fine)
- /*
- //save db filter (if it needs to be considered only):
- if (respectFiltering(baseDirectory, dirInfoLeft))
- dbFilterLeft = dirInfoLeft.filter.get();
-
- if (respectFiltering(baseDirectory, dirInfoRight))
- dbFilterRight = dirInfoRight.filter.get();
- */
- recurse(baseDirectory,
- &dirInfoLeft.baseDirContainer,
- &dirInfoRight.baseDirContainer);
+
+ recurse(baseDirectory, &*lastSyncState);
//----------- detect renamed files -----------------
if (!exLeftOnly.empty() && !exRightOnly.empty())
{
- findEqualDbEntries(dirInfoLeft .baseDirContainer, //fill map "onceEqual"
- dirInfoRight.baseDirContainer);
-
+ collectEqualDbEntries(*lastSyncState); //fill map "onceEqual"
detectRenamedFiles();
}
}
- /*
- static bool respectFiltering(const BaseDirMapping& baseDirectory, const DirInformation& dirInfo)
- {
- //respect filtering if sync-DB filter is active && different from baseDir's filter:
- // in all other cases "view on files" is smaller for baseDirectory(current) than it was for dirInfo(old)
- // => dirInfo can be queried as if it were a scan without filters
- return !dirInfo.filter->isNull() && *dirInfo.filter != *baseDirectory.getFilter();
- }
- */
-
- std::pair<DirInfoPtr, DirInfoPtr> loadDBFile(const BaseDirMapping& baseDirectory) //return nullptr on failure
+ std::shared_ptr<InSyncDir> loadDBFile(const BaseDirMapping& baseMap) //return nullptr on failure
{
try
{
- return loadFromDisk(baseDirectory);
+ return loadLastSynchronousState(baseMap); //throw FileError, FileErrorDatabaseNotExisting
}
catch (FileErrorDatabaseNotExisting&) {} //let's ignore these errors for now...
catch (FileError& error) //e.g. incompatible database version
@@ -455,43 +357,17 @@ private:
reportWarning_(error.toString() + L" \n\n" +
_("Setting default synchronization directions: Old files will be overwritten with newer files."));
}
- return std::pair<DirInfoPtr, DirInfoPtr>();
+ return nullptr;
}
- /*
- bool filterFileConflictFound(const Zstring& relativeName) const
- {
- //if filtering would have excluded file during database creation, then we can't say anything about its former state
- return (dbFilterLeft && !dbFilterLeft ->passFileFilter(relativeName)) ||
- (dbFilterRight && !dbFilterRight->passFileFilter(relativeName));
- }
-
-
- bool filterDirConflictFound(const Zstring& relativeName) const
- {
- //if filtering would have excluded directory during database creation, then we can't say anything about its former state
- return (dbFilterLeft && !dbFilterLeft ->passDirFilter(relativeName, nullptr)) ||
- (dbFilterRight && !dbFilterRight->passDirFilter(relativeName, nullptr));
- }
- */
-
- void recurse(HierarchyObject& hierObj,
- const DirContainer* dbDirectoryLeft,
- const DirContainer* dbDirectoryRight)
+ void recurse(HierarchyObject& hierObj, const InSyncDir* dbContainer)
{
- std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(),
- [&](FileMapping& fileMap) { processFile(fileMap, dbDirectoryLeft, dbDirectoryRight); });
-
- std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(),
- [&](SymLinkMapping& linkMap) { processSymlink(linkMap, dbDirectoryLeft, dbDirectoryRight); });
-
- std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(),
- [&](DirMapping& dirMap) { processDir(dirMap, dbDirectoryLeft, dbDirectoryRight); });
+ std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(), [&](FileMapping& fileMap) { processFile (fileMap, dbContainer); });
+ std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), [&](SymLinkMapping& linkMap) { processSymlink(linkMap, dbContainer); });
+ std::for_each(hierObj.refSubDirs ().begin(), hierObj.refSubDirs ().end(), [&](DirMapping& dirMap) { processDir (dirMap, dbContainer); });
}
- void processFile(FileMapping& fileObj,
- const DirContainer* dbDirectoryLeft,
- const DirContainer* dbDirectoryRight)
+ void processFile(FileMapping& fileObj, const InSyncDir* dbContainer)
{
const CompareFilesResult cat = fileObj.getCategory();
if (cat == FILE_EQUAL)
@@ -511,245 +387,138 @@ private:
//----------------------------------------------------------------------
//##################### schedule old temporary files for deletion ####################
- if (cat == FILE_LEFT_SIDE_ONLY && endsWith(fileObj.getShortName<LEFT_SIDE>(), zen::TEMP_FILE_ENDING))
+ if (cat == FILE_LEFT_SIDE_ONLY && endsWith(fileObj.getShortName<LEFT_SIDE>(), TEMP_FILE_ENDING))
{
fileObj.setSyncDir(SYNC_DIR_LEFT);
return;
}
- else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(fileObj.getShortName<RIGHT_SIDE>(), zen::TEMP_FILE_ENDING))
+ else if (cat == FILE_RIGHT_SIDE_ONLY && endsWith(fileObj.getShortName<RIGHT_SIDE>(), TEMP_FILE_ENDING))
{
fileObj.setSyncDir(SYNC_DIR_RIGHT);
return;
}
//####################################################################################
- /*
- if (filterFileConflictFound(fileObj.getObjRelativeName()))
- {
- 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);
- return;
- }
- */
-
- //determine datasets for change detection
- const DataSetFile dataDbLeft = retrieveDataSetFile(fileObj.getObjShortName(), dbDirectoryLeft);
- const DataSetFile dataDbRight = retrieveDataSetFile(fileObj.getObjShortName(), dbDirectoryRight);
-
- const DataSetFile dataCurrentLeft (fileObj, Int2Type<LEFT_SIDE >());
- const DataSetFile dataCurrentRight(fileObj, Int2Type<RIGHT_SIDE>());
+ //try to find corresponding database entry
+ const InSyncDir::FileList::value_type* dbEntry = nullptr;
+ if (dbContainer)
+ {
+ auto iter = dbContainer->files.find(fileObj.getObjShortName());
+ if (iter != dbContainer->files.end())
+ dbEntry = &*iter;
+ }
//evaluation
- const bool changeOnLeft = dataDbLeft != dataCurrentLeft;
- const bool changeOnRight = dataDbRight != dataCurrentRight;
+ const bool changeOnLeft = !isEqual<LEFT_SIDE >(fileObj, dbEntry);
+ const bool changeOnRight = !isEqual<RIGHT_SIDE>(fileObj, dbEntry);
- if (dataDbLeft == dataDbRight) //we have a "last synchronous state" => last sync seems to have been successful
+ if (changeOnLeft != changeOnRight)
{
- if (changeOnLeft)
- {
- if (changeOnRight)
- fileObj.setSyncDirConflict(txtBothSidesChanged);
- else
- fileObj.setSyncDir(SYNC_DIR_RIGHT);
- }
+ //if database entry not in sync according to current settings! -> do not set direction based on async status!
+ if (dbEntry && !stillInSync(dbEntry->second, cmpVar, fileTimeTolerance))
+ fileObj.setSyncDirConflict(txtDbNotInSync);
else
- {
- if (changeOnRight)
- fileObj.setSyncDir(SYNC_DIR_LEFT);
- else
- fileObj.setSyncDirConflict(txtNoSideChanged);
- }
+ fileObj.setSyncDir(changeOnLeft ? SYNC_DIR_RIGHT : SYNC_DIR_LEFT);
}
- else //object did not complete last sync: important check: user may have changed comparison variant, so what was in sync according to last variant is not any longer!
+ else
{
- if (changeOnLeft && changeOnRight)
+ if (changeOnLeft)
fileObj.setSyncDirConflict(txtBothSidesChanged);
else
- {
- // 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(txtLastSyncFail);
- }
+ fileObj.setSyncDirConflict(txtNoSideChanged);
}
}
-
- void processSymlink(SymLinkMapping& linkObj,
- const DirContainer* dbDirectoryLeft,
- const DirContainer* dbDirectoryRight)
+ void processSymlink(SymLinkMapping& linkObj, const InSyncDir* dbContainer)
{
const CompareSymlinkResult cat = linkObj.getLinkCategory();
if (cat == SYMLINK_EQUAL)
return;
- /*
- if (filterFileConflictFound(linkObj.getObjRelativeName())) //always use file filter: Link type may not be "stable" on Linux!
- {
- if (cat == SYMLINK_LEFT_SIDE_ONLY)
- linkObj.setSyncDir(SYNC_DIR_RIGHT);
- else if (cat == SYMLINK_RIGHT_SIDE_ONLY)
- linkObj.setSyncDir(SYNC_DIR_LEFT);
- else
- linkObj.setSyncDirConflict(txtFilterChanged);
- return;
- }
- */
-
- //determine datasets for change detection
- const DataSetSymlink dataDbLeft = retrieveDataSetSymlink(linkObj.getObjShortName(), dbDirectoryLeft);
- const DataSetSymlink dataDbRight = retrieveDataSetSymlink(linkObj.getObjShortName(), dbDirectoryRight);
-
- const DataSetSymlink dataCurrentLeft( linkObj, Int2Type<LEFT_SIDE>());
- const DataSetSymlink dataCurrentRight(linkObj, Int2Type<RIGHT_SIDE>());
+ //try to find corresponding database entry
+ const InSyncDir::LinkList::value_type* dbEntry = nullptr;
+ if (dbContainer)
+ {
+ auto iter = dbContainer->symlinks.find(linkObj.getObjShortName());
+ if (iter != dbContainer->symlinks.end())
+ dbEntry = &*iter;
+ }
//evaluation
- const bool changeOnLeft = dataDbLeft != dataCurrentLeft;
- const bool changeOnRight = dataDbRight != dataCurrentRight;
+ const bool changeOnLeft = !isEqual<LEFT_SIDE >(linkObj, dbEntry);
+ const bool changeOnRight = !isEqual<RIGHT_SIDE>(linkObj, dbEntry);
- if (dataDbLeft == dataDbRight) //last sync seems to have been successful
+ if (changeOnLeft != changeOnRight)
{
- if (changeOnLeft)
- {
- if (changeOnRight)
- linkObj.setSyncDirConflict(txtBothSidesChanged);
- else
- linkObj.setSyncDir(SYNC_DIR_RIGHT);
- }
+ //if database entry not in sync according to current settings! -> do not set direction based on async status!
+ if (dbEntry && !stillInSync(dbEntry->second, cmpVar))
+ linkObj.setSyncDirConflict(txtDbNotInSync);
else
- {
- if (changeOnRight)
- linkObj.setSyncDir(SYNC_DIR_LEFT);
- else
- linkObj.setSyncDirConflict(txtNoSideChanged);
- }
+ linkObj.setSyncDir(changeOnLeft ? SYNC_DIR_RIGHT : SYNC_DIR_LEFT);
}
- else //object did not complete last sync
+ else
{
- if (changeOnLeft && changeOnRight)
+ if (changeOnLeft)
linkObj.setSyncDirConflict(txtBothSidesChanged);
else
- linkObj.setSyncDirConflict(txtLastSyncFail);
+ linkObj.setSyncDirConflict(txtNoSideChanged);
}
}
-
- void processDir(DirMapping& dirObj,
- const DirContainer* dbDirectoryLeft,
- const DirContainer* dbDirectoryRight)
+ void processDir(DirMapping& dirObj, const InSyncDir* dbContainer)
{
const CompareDirResult cat = dirObj.getDirCategory();
- /*
- if (filterDirConflictFound(dirObj.getObjRelativeName()))
- {
- 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:
- ;
- }
-
- SetDirChangedFilter().recurse(dirObj); //filter issue for this directory => treat subfiles/-dirs the same
- return;
- }
- */
- //determine datasets for change detection
- const std::pair<DataSetDir, const DirContainer*> dataDbLeftStuff = retrieveDataSetDir(dirObj.getObjShortName(), dbDirectoryLeft);
- const std::pair<DataSetDir, const DirContainer*> dataDbRightStuff = retrieveDataSetDir(dirObj.getObjShortName(), dbDirectoryRight);
+ //try to find corresponding database entry
+ const InSyncDir::DirList::value_type* dbEntry = nullptr;
+ if (dbContainer)
+ {
+ auto iter = dbContainer->dirs.find(dirObj.getObjShortName());
+ if (iter != dbContainer->dirs.end())
+ dbEntry = &*iter;
+ }
if (cat != DIR_EQUAL)
{
- const DataSetDir dataCurrentLeft( dirObj, Int2Type<LEFT_SIDE>());
- const DataSetDir dataCurrentRight(dirObj, Int2Type<RIGHT_SIDE>());
-
//evaluation
- const bool changeOnLeft = dataDbLeftStuff.first != dataCurrentLeft;
- const bool changeOnRight = dataDbRightStuff.first != dataCurrentRight;
+ const bool changeOnLeft = !isEqual<LEFT_SIDE >(dirObj, dbEntry);
+ const bool changeOnRight = !isEqual<RIGHT_SIDE>(dirObj, dbEntry);
- if (dataDbLeftStuff.first == dataDbRightStuff.first) //last sync seems to have been successful
+ if (changeOnLeft != changeOnRight)
{
- if (changeOnLeft)
- {
- if (changeOnRight)
- dirObj.setSyncDirConflict(txtBothSidesChanged);
- else
- dirObj.setSyncDir(SYNC_DIR_RIGHT);
- }
+ //if database entry not in sync according to current settings! -> do not set direction based on async status!
+ if (dbEntry && !stillInSync(dbEntry->second))
+ dirObj.setSyncDirConflict(txtDbNotInSync);
else
- {
- if (changeOnRight)
- dirObj.setSyncDir(SYNC_DIR_LEFT);
- else
- {
- assert(false);
- dirObj.setSyncDirConflict(txtNoSideChanged);
- }
- }
+ dirObj.setSyncDir(changeOnLeft ? SYNC_DIR_RIGHT : SYNC_DIR_LEFT);
}
- else //object did not complete last sync
+ else
{
- if (changeOnLeft && changeOnRight)
+ if (changeOnLeft)
dirObj.setSyncDirConflict(txtBothSidesChanged);
else
- {
- // 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);
- // }
-
- dirObj.setSyncDirConflict(txtLastSyncFail);
- }
+ dirObj.setSyncDirConflict(txtNoSideChanged);
}
}
- recurse(dirObj, dataDbLeftStuff.second, dataDbRightStuff.second); //recursion
+ recurse(dirObj, dbEntry ? &dbEntry->second : nullptr); //recursion
}
-
- void findEqualDbEntries(const DirContainer& dbDirectoryLeft,
- const DirContainer& dbDirectoryRight)
+ 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
- std::for_each(dbDirectoryLeft.files.begin(), dbDirectoryLeft.files.end(),
- [&](const DirContainer::FileList::value_type& entryLeft)
+ std::for_each(container.files.begin(), container.files.end(),
+ [&](const std::pair<Zstring, InSyncFile>& filePair)
{
- auto iterRight = dbDirectoryRight.files.find(entryLeft.first);
- if (iterRight != dbDirectoryRight.files.end())
- {
- if (entryLeft. second.id != FileId() &&
- iterRight->second.id != FileId() &&
- DataSetFile(entryLeft.first, entryLeft.second) == DataSetFile(iterRight->first, iterRight->second))
- onceEqual.insert(std::make_pair(getFileIdKey(entryLeft.second), getFileIdKey(iterRight->second)));
- }
+ if (filePair.second.left .id != FileId() &&
+ filePair.second.right.id != FileId() &&
+ stillInSync(filePair.second, cmpVar, fileTimeTolerance))
+ onceEqual.insert(std::make_pair(getFileIdKey(filePair.second.left), getFileIdKey(filePair.second.right)));
});
- std::for_each(dbDirectoryLeft.dirs.begin(), dbDirectoryLeft.dirs.end(),
- [&](const DirContainer::DirList::value_type& entryLeft)
- {
- auto iterRight = dbDirectoryRight.dirs.find(entryLeft.first);
- if (iterRight != dbDirectoryRight.dirs.end())
- findEqualDbEntries(entryLeft.second, iterRight->second);
- });
+ std::for_each(container.dirs.begin(), container.dirs.end(),
+ [&](const std::pair<Zstring, InSyncDir>& dirPair) { collectEqualDbEntries(dirPair.second); });
}
typedef std::tuple<Int64, UInt64, FileId> FileIdKey; //(date, size, file ID)
@@ -793,11 +562,12 @@ private:
});
}
-
const std::wstring txtBothSidesChanged;
const std::wstring txtNoSideChanged;
- const std::wstring txtFilterChanged;
- const std::wstring txtLastSyncFail;
+ const std::wstring txtDbNotInSync;
+
+ const CompareVariant cmpVar;
+ const size_t fileTimeTolerance;
std::function<void(const std::wstring&)> reportWarning_;
@@ -849,12 +619,31 @@ private:
FAT caveat: File Ids are generally not stable when file is either moved or renamed!
=> 1. Move/rename operations on FAT cannot be detected reliably.
- => 2. database generally contains wrong file ID on FAT after renaming from .ffs_tmp files => correct file Ids in database after next sync
+ => 2. database generally contains wrong file ID on FAT after renaming from .ffs_tmp files => correct file Ids in database only after next sync
+ => 3. even exFAT screws up (but less than FAT) and changes IDs after file move. Did they learn nothing from the past?
+
+ Possible refinement
+ -------------------
+ If the file ID is wrong (FAT) or not available, we could at least allow direct association by name, instead of breaking the chain completely: support NTFS -> FAT
+
+ 1. find equal entries in database:
+ std::hash_map: DB* |-> DB* onceEqual
+
+ 2. build alternative mappings if file Id is available for database entries:
+ std::map: FielId |-> DB* leftIdToDbRight
+ std::map: FielId |-> DB* rightIdToDbRight
+
+ 3. collect files on one side during determination of sync directions:
+ std::vector<FileMapping*, DB*> exLeftOnlyToDbRight -> first try to use file Id, if failed associate via file name instead
+ std::hash_map<DB*, FileMapping*> dbRightToexRightOnly ->
+
+ 4. find renamed pairs
*/
};
-
+}
//---------------------------------------------------------------------------------------------------------------
+
std::vector<DirectionConfig> zen::extractDirectionCfg(const MainConfiguration& mainCfg)
{
//merge first and additional pairs
@@ -1415,7 +1204,7 @@ void deleteFromGridAndHDOneSide(InputIterator first, InputIterator last,
{
if (useRecycleBin)
{
- if (zen::moveToRecycleBin(fsObj.getFullName<side>())) //throw FileError
+ if (zen::recycleOrDelete(fsObj.getFullName<side>())) //throw FileError
statusHandler.notifyDeletion(fsObj.getFullName<side>());
}
else
bgstack15