summaryrefslogtreecommitdiff
path: root/synchronization.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'synchronization.cpp')
-rw-r--r--synchronization.cpp522
1 files changed, 294 insertions, 228 deletions
diff --git a/synchronization.cpp b/synchronization.cpp
index daa08284..9dd98776 100644
--- a/synchronization.cpp
+++ b/synchronization.cpp
@@ -9,7 +9,6 @@
#include <deque>
#include <stdexcept>
#include <wx/file.h> //get rid!?
-#include <wx+/string_conv.h>
#include <wx+/format_unit.h>
#include <zen/scope_guard.h>
#include <zen/file_handling.h>
@@ -51,7 +50,7 @@ void SyncStatistics::init()
updateRight = 0;
deleteLeft = 0;
deleteRight = 0;
- conflict = 0;
+ //conflict = 0;
rowsTotal = 0;
}
@@ -137,9 +136,9 @@ void SyncStatistics::calcStats(const FileMapping& fileObj)
break;
case SO_UNRESOLVED_CONFLICT:
- ++conflict;
- if (firstConflicts.size() < 3) //save the first 3 conflict texts
- firstConflicts.push_back(std::make_pair(fileObj.getObjRelativeName(), fileObj.getSyncOpConflict()));
+ //++conflict;
+ //if (conflictMsgs.size() < MAX_CONFLICTS) //save the first conflict texts
+ conflictMsgs.push_back(std::make_pair(fileObj.getObjRelativeName(), fileObj.getSyncOpConflict()));
break;
case SO_COPY_METADATA_TO_LEFT:
@@ -189,9 +188,9 @@ void SyncStatistics::calcStats(const SymLinkMapping& linkObj)
break;
case SO_UNRESOLVED_CONFLICT:
- ++conflict;
- if (firstConflicts.size() < 3) //save the first 3 conflict texts
- firstConflicts.push_back(std::make_pair(linkObj.getObjRelativeName(), linkObj.getSyncOpConflict()));
+ //++conflict;
+ //if (conflictMsgs.size() < MAX_CONFLICTS) //save the first conflict texts
+ conflictMsgs.push_back(std::make_pair(linkObj.getObjRelativeName(), linkObj.getSyncOpConflict()));
break;
case SO_MOVE_LEFT_SOURCE:
@@ -233,9 +232,9 @@ void SyncStatistics::calcStats(const DirMapping& dirObj)
break;
case SO_UNRESOLVED_CONFLICT:
- ++conflict;
- if (firstConflicts.size() < 3) //save the first 3 conflict texts
- firstConflicts.push_back(std::make_pair(dirObj.getObjRelativeName(), dirObj.getSyncOpConflict()));
+ //++conflict;
+ //if (conflictMsgs.size() < MAX_CONFLICTS) //save the first conflict texts
+ conflictMsgs.push_back(std::make_pair(dirObj.getObjRelativeName(), dirObj.getSyncOpConflict()));
break;
case SO_COPY_METADATA_TO_LEFT:
@@ -307,9 +306,7 @@ bool significantDifferenceDetected(const SyncStatistics& folderPairStat)
}
//#################################################################################################################
-#ifdef FFS_WIN
-warn_static("finish")
-#endif
+
/*
class PhysicalStatistics //counts *physical* operations, disk accesses and bytes transferred
{
@@ -455,7 +452,7 @@ public:
const Zstring& subdirShort, //
const Zstring& baseDirPf, //with separator postfix
ProcessCallback& procCallback);
- ~DeletionHandling(); //always (try to) clean up, even if synchronization is aborted!
+ ~DeletionHandling() { try { tryCleanup(); } catch (...) {} /*make sure this stays non-blocking!*/ } //always (try to) clean up, even if synchronization is aborted!
//clean-up temporary directory (recycle bin optimization)
void tryCleanup(); //throw FileError -> call this in non-exceptional coding, i.e. somewhere after sync!
@@ -466,22 +463,34 @@ public:
//in contrast to "removeFolder()" this function will update statistics!
const std::wstring& getTxtRemovingFile () const { return txtRemovingFile; } //
- const std::wstring& getTxtRemovingSymLink() const { return txtRemovingSymlink; } //status text templates
+ const std::wstring& getTxtRemovingSymLink() const { return txtRemovingSymlink; } //buffered status texts
const std::wstring& getTxtRemovingDir () const { return txtRemovingDirectory; } //
//evaluate whether a deletion will actually free space within a volume
bool deletionFreesSpace() const;
+#ifdef FFS_WIN
+ bool recyclerFallbackOnDelete() const { return recFallbackDelPermantently; }
+#endif
private:
void removeFolderInt(const Zstring& relativeName, const int* objectsExpected, const Int64* dataExpected) const; //throw FileError
- DeletionPolicy deletionType;
- ProcessCallback* procCallback_; //always bound! need assignment operator => not a reference
+ void setDeletionPolicy(DeletionPolicy newPolicy);
+
+ ProcessCallback* const procCallback_; //always bound! need assignment operator => not a reference
+ const Zstring baseDirPf_; //ends with path separator
+ const Zstring custDelDir_;
+ const Zstring recyclerTmpDirPf; //temporary folder to move files to, before moving whole folder to recycler (postfixed with file name separator)
+ const Zstring versioningDirPf; //timestamped versioning folder
+
+#ifdef FFS_WIN
+ bool recFallbackDelPermantently;
+#endif
- Zstring sessionDelDirPf; //full target deletion folder for current folder pair (with timestamp, ends with path separator)
- Zstring baseDirPf_; //ends with path separator
+ //magage three states: allow dynamic fallback from recycler to permanent deletion
+ DeletionPolicy deletionPolicy_;
- //preloaded status texts:
+ //buffer status texts:
std::wstring txtRemovingFile;
std::wstring txtRemovingSymlink;
std::wstring txtRemovingDirectory;
@@ -495,17 +504,35 @@ DeletionHandling::DeletionHandling(DeletionPolicy handleDel,
const Zstring& subdirShort, //
const Zstring& baseDirPf, //with separator postfix
ProcessCallback& procCallback) :
- deletionType(handleDel),
procCallback_(&procCallback),
baseDirPf_(baseDirPf),
+ custDelDir_(custDelDir),
+ recyclerTmpDirPf(!baseDirPf_.empty() ? appendSeparator(findUnusedName(baseDirPf_ + Zstr("FFS ") + formatTime<Zstring>(Zstr("%Y-%m-%d %H%M%S")))) : Zstring()),
+ versioningDirPf (!custDelDir.empty() ? appendSeparator(findUnusedName(appendSeparator(custDelDir) + subdirShort)) : Zstring()),
+#ifdef FFS_WIN
+ recFallbackDelPermantently(false),
+#endif
+ deletionPolicy_(MOVE_TO_RECYCLE_BIN),
cleanedUp(false)
{
#ifdef FFS_WIN
- if (baseDirPf.empty() ||
- (deletionType == MOVE_TO_RECYCLE_BIN && recycleBinStatus(baseDirPf) == STATUS_REC_MISSING))
- deletionType = DELETE_PERMANENTLY; //Windows' ::SHFileOperation() will do this anyway, but we have a better and faster deletion routine (e.g. on networks)
+ if (!baseDirPf.empty())
+ if (handleDel == MOVE_TO_RECYCLE_BIN && recycleBinStatus(baseDirPf) != STATUS_REC_EXISTS)
+ {
+ handleDel = DELETE_PERMANENTLY; //Windows' ::SHFileOperation() will do this anyway, but we have a better and faster deletion routine (e.g. on networks)
+ recFallbackDelPermantently = true;
+ }
#endif
- switch (deletionType)
+
+ setDeletionPolicy(handleDel);
+}
+
+
+void DeletionHandling::setDeletionPolicy(DeletionPolicy newPolicy)
+{
+ deletionPolicy_ = newPolicy;
+
+ switch (deletionPolicy_)
{
case DELETE_PERMANENTLY:
txtRemovingFile = _("Deleting file %x" );
@@ -514,40 +541,27 @@ DeletionHandling::DeletionHandling(DeletionPolicy handleDel,
break;
case MOVE_TO_RECYCLE_BIN:
- if (!baseDirPf_.empty())
- sessionDelDirPf = appendSeparator(findUnusedName(baseDirPf_ + Zstr("FFS ") + formatTime<Zstring>(Zstr("%Y-%m-%d %H%M%S"))));
-
txtRemovingFile = _("Moving file %x to recycle bin" );
txtRemovingDirectory = _("Moving folder %x to recycle bin" );
txtRemovingSymlink = _("Moving symbolic link %x to recycle bin");
break;
case MOVE_TO_CUSTOM_DIRECTORY:
- if (!custDelDir.empty())
- sessionDelDirPf = appendSeparator(findUnusedName(appendSeparator(custDelDir) + subdirShort));
-
- txtRemovingFile = replaceCpy(_("Moving file %x to %y" ), L"%y", fmtFileName(custDelDir));
- txtRemovingDirectory = replaceCpy(_("Moving folder %x to %y" ), L"%y", fmtFileName(custDelDir));
- txtRemovingSymlink = replaceCpy(_("Moving symbolic link %x to %y"), L"%y", fmtFileName(custDelDir));
+ txtRemovingFile = replaceCpy(_("Moving file %x to %y" ), L"%y", fmtFileName(custDelDir_));
+ txtRemovingDirectory = replaceCpy(_("Moving folder %x to %y" ), L"%y", fmtFileName(custDelDir_));
+ txtRemovingSymlink = replaceCpy(_("Moving symbolic link %x to %y"), L"%y", fmtFileName(custDelDir_));
break;
}
}
-DeletionHandling::~DeletionHandling()
-{
- //always (try to) clean up, even if synchronization is aborted!
- try { tryCleanup(); }
- catch (...) {} //make sure this stays non-blocking!
-}
-
-
void DeletionHandling::tryCleanup() //throw FileError
{
if (!cleanedUp)
{
- if (deletionType == MOVE_TO_RECYCLE_BIN) //clean-up temporary directory (recycle bin)
- zen::moveToRecycleBin(beforeLast(sessionDelDirPf, FILE_NAME_SEPARATOR)); //throw FileError
+ if (deletionPolicy_ == MOVE_TO_RECYCLE_BIN) //clean-up temporary directory (recycle bin)
+ if (!recyclerTmpDirPf.empty()) //empty folder input pair
+ recycleOrDelete(beforeLast(recyclerTmpDirPf, FILE_NAME_SEPARATOR)); //throw FileError
cleanedUp = true;
}
@@ -637,15 +651,16 @@ void DeletionHandling::removeFile(const Zstring& relativeName) const
{
const Zstring fullName = baseDirPf_ + relativeName;
- switch (deletionType)
+ switch (deletionPolicy_)
{
case DELETE_PERMANENTLY:
zen::removeFile(fullName);
+ //[!] resolve nameclash!
break;
case MOVE_TO_RECYCLE_BIN:
{
- const Zstring targetFile = sessionDelDirPf + relativeName; //ends with path separator
+ const Zstring targetFile = recyclerTmpDirPf + relativeName; //ends with path separator
try
{
@@ -660,14 +675,16 @@ void DeletionHandling::removeFile(const Zstring& relativeName) const
{
const Zstring targetDir = beforeLast(targetFile, FILE_NAME_SEPARATOR);
if (!dirExists(targetDir)) //no reason to update gui or overwrite status text!
- createDirectory(targetDir); //throw FileError -> may legitimately fail on Linux if permissions are missing
+ {
+ makeDirectory(targetDir); //throw FileError -> may legitimately fail on Linux if permissions are missing
+ renameFile(fullName, targetFile); //throw FileError -> this should work now!
+ }
else
throw;
- renameFile(fullName, targetFile); //throw FileError -> this should work now!
}
catch (FileError&) //if anything went wrong, move to recycle bin the standard way (single file processing: slow)
{
- moveToRecycleBin(fullName); //throw FileError
+ recycleOrDelete(fullName); //throw FileError
}
}
}
@@ -676,7 +693,7 @@ void DeletionHandling::removeFile(const Zstring& relativeName) const
case MOVE_TO_CUSTOM_DIRECTORY:
{
CallbackMoveFileImpl callBack(*procCallback_, *this, nullptr); //we do *not* report statistics in this method
- const Zstring targetFile = sessionDelDirPf + relativeName; //ends with path separator
+ const Zstring targetFile = versioningDirPf + relativeName; //ends with path separator
try //... to get away cheaply!
{
@@ -689,7 +706,7 @@ void DeletionHandling::removeFile(const Zstring& relativeName) const
const Zstring targetDir = beforeLast(targetFile, FILE_NAME_SEPARATOR);
if (!dirExists(targetDir))
{
- createDirectory(targetDir); //throw FileError
+ makeDirectory(targetDir); //throw FileError
moveFile(fullName, targetFile, &callBack); //throw FileError -> this should work now!
}
else
@@ -708,9 +725,9 @@ void DeletionHandling::removeFolderInt(const Zstring& relativeName, const int* o
int objectsReported = 0; //use *only* if "objectsExpected" is bound!
//in error situation: undo communication of processed amount of data
- zen::ScopeGuard guardStatistics = zen::makeGuard([&] { procCallback_->updateProcessedData(-objectsReported, 0); });
+ ScopeGuard guardStatistics = makeGuard([&] { procCallback_->updateProcessedData(-objectsReported, 0); });
- switch (deletionType)
+ switch (deletionPolicy_)
{
case DELETE_PERMANENTLY:
{
@@ -721,7 +738,7 @@ void DeletionHandling::removeFolderInt(const Zstring& relativeName, const int* o
case MOVE_TO_RECYCLE_BIN:
{
- const Zstring targetDir = sessionDelDirPf + relativeName;
+ const Zstring targetDir = recyclerTmpDirPf + relativeName;
try
{
@@ -736,14 +753,16 @@ void DeletionHandling::removeFolderInt(const Zstring& relativeName, const int* o
{
const Zstring targetSuperDir = beforeLast(targetDir, FILE_NAME_SEPARATOR);
if (!dirExists(targetSuperDir)) //no reason to update gui or overwrite status text!
- createDirectory(targetSuperDir); //throw FileError -> may legitimately fail on Linux if permissions are missing
+ {
+ makeDirectory(targetSuperDir); //throw FileError -> may legitimately fail on Linux if permissions are missing
+ renameFile(fullName, targetDir); //throw FileError -> this should work now!
+ }
else
throw;
- renameFile(fullName, targetDir); //throw FileError -> this should work now!
}
catch (FileError&) //if anything went wrong, move to recycle bin the standard way (single file processing: slow)
{
- moveToRecycleBin(fullName); //throw FileError
+ recycleOrDelete(fullName); //throw FileError
}
}
}
@@ -758,7 +777,7 @@ void DeletionHandling::removeFolderInt(const Zstring& relativeName, const int* o
case MOVE_TO_CUSTOM_DIRECTORY:
{
CallbackMoveFileImpl callBack(*procCallback_, *this, objectsExpected ? &objectsReported : nullptr);
- const Zstring targetDir = sessionDelDirPf + relativeName;
+ const Zstring targetDir = versioningDirPf + relativeName;
try //... to get away cheaply!
{
@@ -771,7 +790,7 @@ void DeletionHandling::removeFolderInt(const Zstring& relativeName, const int* o
const Zstring targetSuperDir = beforeLast(targetDir, FILE_NAME_SEPARATOR);
if (!dirExists(targetSuperDir))
{
- createDirectory(targetSuperDir); //throw FileError
+ makeDirectory(targetSuperDir); //throw FileError
moveDirectory(fullName, targetDir, &callBack); //throw FileError -> this should work now!
}
else
@@ -796,14 +815,14 @@ void DeletionHandling::removeFolderInt(const Zstring& relativeName, const int* o
//evaluate whether a deletion will actually free space within a volume
bool DeletionHandling::deletionFreesSpace() const
{
- switch (deletionType)
+ switch (deletionPolicy_)
{
case DELETE_PERMANENTLY:
return true;
case MOVE_TO_RECYCLE_BIN:
return false; //in general... (unless Recycle Bin is full)
case MOVE_TO_CUSTOM_DIRECTORY:
- switch (zen::onSameVolume(baseDirPf_, sessionDelDirPf))
+ switch (zen::onSameVolume(baseDirPf_, versioningDirPf))
{
case IS_SAME_YES:
return false;
@@ -1076,13 +1095,13 @@ void SynchronizeFolderPair::prepare2StepMove(FileMapping& sourceObj,
{
const Zstring& source = sourceObj.getFullName<side>();
const Zstring& tmpTarget = findUnusedTempName(sourceObj.getBaseDirPf<side>() + sourceObj.getShortName<side>());
- //this could still lead to a name-clash in obscure cases, if some file ex. on the other side with
+ //this could still lead to a name-clash in obscure cases, if some file exists on the other side with
//the very same (.ffs_tmp) name and is copied before the second step of the move is executed
//good news: even in this pathologic case, this may only prevent the copy of the other file, but not the move
reportInfo(replaceCpy(txtMovingFile, L"%y", fmtFileName(tmpTarget)), source);
- renameFile(source, tmpTarget); //throw FileError;
+ renameFile(source, tmpTarget); //throw FileError
//update file hierarchy
const FileDescriptor descrSource(sourceObj.getLastWriteTime<side>(),
@@ -1502,7 +1521,7 @@ void SynchronizeFolderPair::synchronizeFileInt(FileMapping& fileObj, SyncOperati
reportInfo(replaceCpy(txtMovingFile, L"%y", fmtFileName(target)), source);
- renameFile(source, target); //throw FileError;
+ renameFile(source, target); //throw FileError
const FileDescriptor descrTarget(sourceObj->getLastWriteTime<sideTrg>(),
sourceObj->getFileSize <sideTrg>(),
@@ -1549,7 +1568,7 @@ void SynchronizeFolderPair::synchronizeFileInt(FileMapping& fileObj, SyncOperati
if (fileObj.getShortName<sideTrg>() != fileObj.getShortName<sideSrc>()) //adapt difference in case (windows only)
renameFile(fileObj.getFullName<sideTrg>(),
- beforeLast(fileObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName<sideSrc>()); //throw FileError;
+ beforeLast(fileObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName<sideSrc>()); //throw FileError
if (!sameFileTime(fileObj.getLastWriteTime<sideTrg>(), fileObj.getLastWriteTime<sideSrc>(), 2)) //respect 2 second FAT/FAT32 precision
setFileTime(fileObj.getFullName<sideTrg>(), fileObj.getLastWriteTime<sideSrc>(), SYMLINK_FOLLOW); //throw FileError
@@ -1673,7 +1692,7 @@ void SynchronizeFolderPair::synchronizeLinkInt(SymLinkMapping& linkObj, SyncOper
if (linkObj.getShortName<sideTrg>() != linkObj.getShortName<sideSrc>()) //adapt difference in case (windows only)
renameFile(linkObj.getFullName<sideTrg>(),
- beforeLast(linkObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + linkObj.getShortName<sideSrc>()); //throw FileError;
+ beforeLast(linkObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + linkObj.getShortName<sideSrc>()); //throw FileError
if (!sameFileTime(linkObj.getLastWriteTime<sideTrg>(), linkObj.getLastWriteTime<sideSrc>(), 2)) //respect 2 second FAT/FAT32 precision
setFileTime(linkObj.getFullName<sideTrg>(), linkObj.getLastWriteTime<sideSrc>(), SYMLINK_DIRECT); //throw FileError
@@ -1729,12 +1748,17 @@ void SynchronizeFolderPair::synchronizeFolderInt(DirMapping& dirObj, SyncOperati
const Zstring& target = dirObj.getBaseDirPf<sideTrg>() + dirObj.getRelativeName<sideSrc>();
reportInfo(txtCreatingFolder, target);
- createDirectory(target, dirObj.getFullName<sideSrc>(), copyFilePermissions_); //no symlink copying!
+ try
+ {
+ makeNewDirectory(target, dirObj.getFullName<sideSrc>(), copyFilePermissions_); //no symlink copying!
+ }
+ catch (const ErrorTargetExisting&) { if (!dirExists(target)) throw; } //clash with file (dir-symlink is okay)
+
dirObj.copyTo<sideTrg>(); //update DirMapping
procCallback_.updateProcessedData(1, 0);
}
- else //source deleted meanwhile...nothing was done (logical point of view!)
+ else //source deleted meanwhile...nothing was done (logical point of view!) -> uh....what about a temporary network drop???
{
// throw FileError
const SyncStatistics subStats(dirObj);
@@ -1753,7 +1777,7 @@ void SynchronizeFolderPair::synchronizeFolderInt(DirMapping& dirObj, SyncOperati
if (dirObj.getShortName<sideTrg>() != dirObj.getShortName<sideSrc>()) //adapt difference in case (windows only)
renameFile(dirObj.getFullName<sideTrg>(),
- beforeLast(dirObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + dirObj.getShortName<sideSrc>()); //throw FileError;
+ beforeLast(dirObj.getFullName<sideTrg>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + dirObj.getShortName<sideSrc>()); //throw FileError
//copyFileTimes -> useless at this time: modification time changes with each child-object creation/deletion
dirObj.copyTo<sideTrg>(); //-> both sides *should* be completely equal now...
@@ -1843,7 +1867,7 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf
//initialize deletion handling: already required when checking for warnings
- std::vector<std::pair<DeletionHandling, DeletionHandling>> delHandler;
+ FixedList<std::pair<DeletionHandling, DeletionHandling>> delHandler;
for (auto j = begin(folderCmp); j != end(folderCmp); ++j)
{
const size_t folderIndex = j - folderCmp.begin();
@@ -1852,20 +1876,20 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf
const Zstring subDirShort = folderCmp.size() <= 1 ? custDelDirShortname : custDelDirShortname + Zstr(" (") + numberTo<Zstring>(folderIndex + 1) + Zstr(")");
//e.g. "SyncJob 2012-05-15 131513 (1)" -> enforce different custom deletion dir when using multiple folder pairs!!!
- delHandler.push_back(std::make_pair(DeletionHandling(folderPairCfg.handleDeletion,
- folderPairCfg.custDelFolder,
- subDirShort,
- j->getBaseDirPf<LEFT_SIDE>(),
- procCallback),
-
- DeletionHandling(folderPairCfg.handleDeletion,
- folderPairCfg.custDelFolder,
- subDirShort,
- j->getBaseDirPf<RIGHT_SIDE>(),
- procCallback)));
+ delHandler.emplace_back(DeletionHandling(folderPairCfg.handleDeletion,
+ folderPairCfg.custDelFolder,
+ subDirShort,
+ j->getBaseDirPf<LEFT_SIDE>(),
+ procCallback),
+
+ DeletionHandling(folderPairCfg.handleDeletion,
+ folderPairCfg.custDelFolder,
+ subDirShort,
+ j->getBaseDirPf<RIGHT_SIDE>(),
+ procCallback));
}
- //-------------------some basic checks:------------------------------------------
+ //-------------------execute basic all at once before starting sync--------------------------------------
auto dependentDir = [](const Zstring& lhs, const Zstring& rhs) //note: this is NOT an equivalence relation!
{
@@ -1902,148 +1926,158 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf
typedef std::vector<std::pair<Zstring, std::pair<Int64, Int64>>> DirSpaceRequAvailList; //dirname / space required / space available
DirSpaceRequAvailList diskSpaceMissing;
- std::set<Zstring> recyclMissing;
+#ifdef FFS_WIN
+ std::set<Zstring, LessFilename> recyclMissing;
+#endif
//start checking folder pairs
- for (auto j = begin(folderCmp); j != end(folderCmp); ++j)
{
- const size_t folderIndex = j - begin(folderCmp);
+ auto iterDelHandler = delHandler.cbegin();
+ for (auto j = begin(folderCmp); j != end(folderCmp); ++j, ++iterDelHandler)
+ {
+ const size_t folderIndex = j - begin(folderCmp);
- //exclude some pathological case (leftdir, rightdir are empty)
- if (EqualFilename()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>()))
- continue;
+ //exclude some pathological case (leftdir, rightdir are empty)
+ if (EqualFilename()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>()))
+ continue;
- const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex];
- const std::pair<DeletionHandling, DeletionHandling>& delHandlerFp = delHandler[folderIndex];
+ const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex];
- const SyncStatistics folderPairStat(*j);
+ const SyncStatistics folderPairStat(*j);
- //aggregate basic information
- const bool writeLeft = folderPairStat.getCreate<LEFT_SIDE>() +
- folderPairStat.getUpdate<LEFT_SIDE>() +
- folderPairStat.getDelete<LEFT_SIDE>() > 0;
+ //aggregate basic information
+ const bool writeLeft = folderPairStat.getCreate<LEFT_SIDE>() +
+ folderPairStat.getUpdate<LEFT_SIDE>() +
+ folderPairStat.getDelete<LEFT_SIDE>() > 0;
- const bool writeRight = folderPairStat.getCreate<RIGHT_SIDE>() +
- folderPairStat.getUpdate<RIGHT_SIDE>() +
- folderPairStat.getDelete<RIGHT_SIDE>() > 0;
+ const bool writeRight = folderPairStat.getCreate<RIGHT_SIDE>() +
+ folderPairStat.getUpdate<RIGHT_SIDE>() +
+ folderPairStat.getDelete<RIGHT_SIDE>() > 0;
- //skip folder pair if there is nothing to do (except for automatic mode, where data base needs to be written even in this case)
- if (!writeLeft && !writeRight &&
- !folderPairCfg.inAutomaticMode)
- {
- skipFolderPair[folderIndex] = true; //skip creating (not yet existing) base directories in particular if there's no need
- continue;
- }
+ //skip folder pair if there is nothing to do (except for automatic mode, where data base needs to be written even in this case)
+ if (!writeLeft && !writeRight &&
+ !folderPairCfg.inAutomaticMode)
+ {
+ skipFolderPair[folderIndex] = true; //skip creating (not yet existing) base directories in particular if there's no need
+ continue;
+ }
- //check empty input fields: basically this only makes sense if empty field is not target (and not automatic mode: because of db file creation)
- if ((j->getBaseDirPf<LEFT_SIDE >().empty() && (writeLeft || folderPairCfg.inAutomaticMode)) ||
- (j->getBaseDirPf<RIGHT_SIDE>().empty() && (writeRight || folderPairCfg.inAutomaticMode)))
- {
- procCallback.reportFatalError(_("Target folder name must not be empty."));
- skipFolderPair[folderIndex] = true;
- continue;
- }
-
- //aggregate information of folders used by multiple pairs in read/write access
- if (!dependentDir(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())) //true in general
- {
- if (writeLeft && writeRight)
+ //check empty input fields: basically this only makes sense if empty field is not target (and not automatic mode: because of db file creation)
+ if ((j->getBaseDirPf<LEFT_SIDE >().empty() && (writeLeft || folderPairCfg.inAutomaticMode)) ||
+ (j->getBaseDirPf<RIGHT_SIDE>().empty() && (writeRight || folderPairCfg.inAutomaticMode)))
{
- incWriteCount(j->getBaseDirPf<LEFT_SIDE >());
- incWriteCount(j->getBaseDirPf<RIGHT_SIDE>());
+ procCallback.reportFatalError(_("Target folder input field must not be empty."));
+ skipFolderPair[folderIndex] = true;
+ continue;
}
- else if (writeLeft)
+
+ //aggregate information of folders used by multiple pairs in read/write access
+ if (!dependentDir(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())) //true in general
{
- incWriteCount(j->getBaseDirPf<LEFT_SIDE>());
- incReadCount (j->getBaseDirPf<RIGHT_SIDE>());
+ if (writeLeft && writeRight)
+ {
+ incWriteCount(j->getBaseDirPf<LEFT_SIDE >());
+ incWriteCount(j->getBaseDirPf<RIGHT_SIDE>());
+ }
+ else if (writeLeft)
+ {
+ incWriteCount(j->getBaseDirPf<LEFT_SIDE>());
+ incReadCount (j->getBaseDirPf<RIGHT_SIDE>());
+ }
+ else if (writeRight)
+ {
+ incReadCount (j->getBaseDirPf<LEFT_SIDE>());
+ incWriteCount(j->getBaseDirPf<RIGHT_SIDE>());
+ }
}
- else if (writeRight)
+ else //if folder pair contains two dependent folders, a warning was already issued after comparison; in this context treat as one write access at most
{
- incReadCount (j->getBaseDirPf<LEFT_SIDE>());
- incWriteCount(j->getBaseDirPf<RIGHT_SIDE>());
+ if (writeLeft || writeRight)
+ incWriteCount(j->getBaseDirPf<LEFT_SIDE>());
}
- }
- else //if folder pair contains two dependent folders, a warning was already issued after comparison; in this context treat as one write access at most
- {
- if (writeLeft || writeRight)
- incWriteCount(j->getBaseDirPf<LEFT_SIDE>());
- }
- if (folderPairStat.getUpdate() + folderPairStat.getDelete() > 0)
- {
- if (folderPairCfg.handleDeletion == zen::MOVE_TO_CUSTOM_DIRECTORY)
+ if (folderPairStat.getUpdate() + folderPairStat.getDelete() > 0)
{
- //check if user-defined directory for deletion was specified
- if (folderPairCfg.custDelFolder.empty())
+ if (folderPairCfg.handleDeletion == zen::MOVE_TO_CUSTOM_DIRECTORY)
{
- procCallback.reportFatalError(_("Folder name for file versioning must not be empty."));
- skipFolderPair[folderIndex] = true;
- continue;
+ //check if user-defined directory for deletion was specified
+ if (folderPairCfg.custDelFolder.empty())
+ {
+ procCallback.reportFatalError(_("Folder input field for versioning must not be empty."));
+ skipFolderPair[folderIndex] = true;
+ continue;
+ }
}
}
- }
- //avoid data loss when source directory doesn't (temporarily?) exist anymore AND user chose to ignore errors (else we wouldn't arrive here)
- if (folderPairStat.getCreate() +
- folderPairStat.getUpdate() +
- folderPairStat.getConflict() == 0 &&
- folderPairStat.getDelete() > 0) //deletions only... (respect filtered items!)
- {
- Zstring missingSrcDir;
- if (!j->getBaseDirPf<LEFT_SIDE>().empty() && !j->wasExisting<LEFT_SIDE>()) //important: we need to evaluate existence status from time of comparison!
- missingSrcDir = j->getBaseDirPf<LEFT_SIDE>();
- if (!j->getBaseDirPf<RIGHT_SIDE>().empty() && !j->wasExisting<RIGHT_SIDE>())
- missingSrcDir = j->getBaseDirPf<RIGHT_SIDE>();
-
- if (!missingSrcDir.empty())
+ //the following scenario is covered by base directory creation below in case source directory exists (accessible or not), but latter doesn't cover not-yet-created source!!!
+ auto checkSourceMissing = [&](const Zstring& baseDirPf, bool wasExisting) -> bool //avoid race-condition: we need to evaluate existence status from time of comparison!
{
- procCallback.reportFatalError(replaceCpy(_("Source directory %x not found."), L"%x", fmtFileName(missingSrcDir)));
- skipFolderPair[folderIndex] = true;
+ const Zstring dirname = beforeLast(baseDirPf, FILE_NAME_SEPARATOR);
+ if (!dirname.empty())
+ {
+ //PERMANENT network drop: avoid data loss when source directory is not found AND user chose to ignore errors (else we wouldn't arrive here)
+ if (folderPairStat.getCreate() +
+ folderPairStat.getUpdate() == 0 &&
+ folderPairStat.getDelete() > 0) //deletions only... (respect filtered items!)
+ //folderPairStat.getConflict() == 0 && -> there COULD be conflicts for <automatic> if directory existence check fails, but loading sync.ffs_db succeeds
+ //https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3531351&group_id=234430 -> fixed, but still better not consider conflicts
+ {
+ if (!wasExisting) //avoid race-condition: we need to evaluate existence status from time of comparison!
+ {
+ procCallback.reportFatalError(replaceCpy(_("Source folder %x not found."), L"%x", fmtFileName(baseDirPf)));
+ skipFolderPair[folderIndex] = true;
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+ if (!checkSourceMissing(j->getBaseDirPf<LEFT_SIDE >(), j->wasExisting<LEFT_SIDE >()) ||
+ !checkSourceMissing(j->getBaseDirPf<RIGHT_SIDE>(), j->wasExisting<RIGHT_SIDE>()))
continue;
- }
- }
- //check if more than 50% of total number of files/dirs are to be created/overwritten/deleted
- if (significantDifferenceDetected(folderPairStat))
- significantDiff.push_back(std::make_pair(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>()));
+ //check if more than 50% of total number of files/dirs are to be created/overwritten/deleted
+ if (significantDifferenceDetected(folderPairStat))
+ significantDiff.push_back(std::make_pair(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>()));
- //check for sufficient free diskspace
- auto checkSpace = [&](const Zstring& baseDirPf, const Int64& spaceRequired)
- {
- try
+ //check for sufficient free diskspace
+ auto checkSpace = [&](const Zstring& baseDirPf, const Int64& spaceRequired)
{
- Int64 freeSpace = to<Int64>(getFreeDiskSpace(baseDirPf)); //throw FileError
+ try
+ {
+ Int64 freeSpace = to<Int64>(getFreeDiskSpace(baseDirPf)); //throw FileError
- if (0 < freeSpace && //zero disk space is either an error or not: in both cases this warning message is obsolete (WebDav seems to report 0)
- freeSpace < spaceRequired)
- diskSpaceMissing.push_back(std::make_pair(baseDirPf, std::make_pair(spaceRequired, freeSpace)));
- }
- catch (FileError&) {}
- };
- const std::pair<Int64, Int64> spaceNeeded = DiskSpaceNeeded::calculate(*j, delHandlerFp.first.deletionFreesSpace(),
- delHandlerFp.second.deletionFreesSpace());
- checkSpace(j->getBaseDirPf<LEFT_SIDE >(), spaceNeeded.first);
- checkSpace(j->getBaseDirPf<RIGHT_SIDE>(), spaceNeeded.second);
+ if (0 < freeSpace && //zero disk space is either an error or not: in both cases this warning message is obsolete (WebDav seems to report 0)
+ freeSpace < spaceRequired)
+ diskSpaceMissing.push_back(std::make_pair(baseDirPf, std::make_pair(spaceRequired, freeSpace)));
+ }
+ catch (FileError&) {}
+ };
+ const std::pair<Int64, Int64> spaceNeeded = DiskSpaceNeeded::calculate(*j,
+ iterDelHandler->first .deletionFreesSpace(),
+ iterDelHandler->second.deletionFreesSpace());
+ checkSpace(j->getBaseDirPf<LEFT_SIDE >(), spaceNeeded.first);
+ checkSpace(j->getBaseDirPf<RIGHT_SIDE>(), spaceNeeded.second);
#ifdef FFS_WIN
- //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong
- if (folderPairCfg.handleDeletion == MOVE_TO_RECYCLE_BIN)
- {
- if (folderPairStat.getUpdate<LEFT_SIDE>() +
- folderPairStat.getDelete<LEFT_SIDE>() > 0 &&
-
- recycleBinStatus(j->getBaseDirPf<LEFT_SIDE>()) != STATUS_REC_EXISTS)
- recyclMissing.insert(j->getBaseDirPf<LEFT_SIDE>());
-
- if (folderPairStat.getUpdate<RIGHT_SIDE>() +
- folderPairStat.getDelete<RIGHT_SIDE>() > 0 &&
-
- recycleBinStatus(j->getBaseDirPf<RIGHT_SIDE>()) != STATUS_REC_EXISTS)
- recyclMissing.insert(j->getBaseDirPf<RIGHT_SIDE>());
- }
+ //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong
+ if (folderPairCfg.handleDeletion == MOVE_TO_RECYCLE_BIN)
+ {
+ if (folderPairStat.getUpdate<LEFT_SIDE>() +
+ folderPairStat.getDelete<LEFT_SIDE>() > 0 &&
+ iterDelHandler->first.recyclerFallbackOnDelete())
+ recyclMissing.insert(j->getBaseDirPf<LEFT_SIDE>());
+
+ if (folderPairStat.getUpdate<RIGHT_SIDE>() +
+ folderPairStat.getDelete<RIGHT_SIDE>() > 0 &&
+ iterDelHandler->second.recyclerFallbackOnDelete())
+ recyclMissing.insert(j->getBaseDirPf<RIGHT_SIDE>());
+ }
#endif
+ }
}
//check if unresolved conflicts exist
@@ -2053,12 +2087,12 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf
std::wstring warningMessage = _("Unresolved conflicts existing!") +
L" (" + toGuiString(statisticsTotal.getConflict()) + L")\n\n";
- const auto& firstConflicts = statisticsTotal.getFirstConflicts(); //get first few sync conflicts
- for (auto iter = firstConflicts.begin(); iter != firstConflicts.end(); ++iter)
+ const auto& conflictMsgs = statisticsTotal.getConflictMessages(); //get first few sync conflicts
+ for (auto iter = conflictMsgs.begin(); iter != conflictMsgs.end(); ++iter)
warningMessage += fmtFileName(iter->first) + L": " + iter->second + L"\n\n";
- if (statisticsTotal.getConflict() > static_cast<int>(firstConflicts.size()))
- warningMessage += L"[...]\n\n";
+ // if (statisticsTotal.getConflict() > static_cast<int>(conflictMsgs.size()))
+ // warningMessage += L"[...]\n\n";
warningMessage += _("You can ignore conflicts and continue synchronization.");
@@ -2090,8 +2124,8 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf
for (auto i = diskSpaceMissing.begin(); i != diskSpaceMissing.end(); ++i)
warningMessage += std::wstring(L"\n\n") +
fmtFileName(i->first) + L"\n" +
- _("Free disk space required:") + L" " + filesizeToShortString(i->second.first) + L"\n" +
- _("Free disk space available:") + L" " + filesizeToShortString(i->second.second);
+ _("Required:") + L" " + filesizeToShortString(i->second.first) + L"\n" +
+ _("Available:") + L" " + filesizeToShortString(i->second.second);
procCallback.reportWarning(warningMessage, m_warnings.warningNotEnoughDiskSpace);
}
@@ -2139,8 +2173,13 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf
try
{
//loop through all directory pairs
- for (auto j = begin(folderCmp); j != end(folderCmp); ++j)
+ auto iterDelHandler = delHandler.begin();
+ for (auto j = begin(folderCmp); j != end(folderCmp); ++j, ++iterDelHandler)
{
+ //exclude some pathological case (leftdir, rightdir are empty)
+ if (EqualFilename()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>()))
+ continue;
+
//------------------------------------------------------------------------------------------
//always report folder pairs for log file, even if there is no work to do
std::wstring left = _("Left") + L": ";
@@ -2154,38 +2193,66 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf
//------------------------------------------------------------------------------------------
const size_t folderIndex = j - begin(folderCmp);
-
const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex];
- std::pair<DeletionHandling, DeletionHandling>& delHandlerFp = delHandler[folderIndex];
if (skipFolderPair[folderIndex]) //folder pairs may be skipped after fatal errors were found
continue;
- //exclude some pathological case (leftdir, rightdir are empty)
- if (EqualFilename()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>()))
- continue;
-
- //create base directories first (if not yet existing) -> no symlink or attribute copying! -> single error message instead of one per file (e.g. unplugged network drive)
- const Zstring dirnameLeft = beforeLast(j->getBaseDirPf<LEFT_SIDE>(), FILE_NAME_SEPARATOR);
- if (!dirnameLeft.empty() && !dirExistsUpdating(dirnameLeft, false, procCallback))
+ //create base directories first (if not yet existing) -> no symlink or attribute copying!
+ auto createDir = [&](const Zstring& baseDirPf, bool wasExisting) -> bool
{
- if (!tryReportingError([&] { createDirectory(dirnameLeft); }, procCallback)) //may throw in error-callback!
- continue; //skip this folder pair
- }
- const Zstring dirnameRight = beforeLast(j->getBaseDirPf<RIGHT_SIDE>(), FILE_NAME_SEPARATOR);
- if (!dirnameRight.empty() && !dirExistsUpdating(dirnameRight, false, procCallback))
- {
- if (!tryReportingError([&] { createDirectory(dirnameRight); }, procCallback)) //may throw in error-callback!
- continue; //skip this folder pair
- }
+ const Zstring dirname = beforeLast(baseDirPf, FILE_NAME_SEPARATOR);
+ if (!dirname.empty())
+ {
+ if (wasExisting) //atomicity: do NOT check directory existence again!
+ {
+ //just convenience: exit sync right here instead of showing tons of error messages during file copy
+ return tryReportingError([&]
+ {
+ if (!dirExistsUpdating(dirname, false, procCallback))
+ throw FileError(replaceCpy(_("Cannot find folder %x."), L"%x", fmtFileName(dirname))); //this should really be a "fatal error"
+ }, procCallback); //may throw in error-callback!
+ }
+ else //create target directory: user presumably ignored error "dir existing" in order to have it created automatically
+ {
+ bool temporaryNetworkDrop = false;
+ bool rv = tryReportingError([&]
+ {
+ try
+ {
+ makeNewDirectory(dirname, Zstring(), false); //FileError, ErrorTargetExisting
+ //a nice race-free check and set operation!
+ }
+ catch (const ErrorTargetExisting&)
+ {
+ //TEMPORARY network drop: base directory not found during comparison, but reappears during synchronization
+ //=> sync-directions are based on false assumptions! Abort.
+ procCallback.reportFatalError(replaceCpy(_("Target folder %x already existing."), L"%x", fmtFileName(baseDirPf)));
+ temporaryNetworkDrop = true;
+
+ //Is it possible we're catching a "false-positive" here, could FFS have created the directory indirectly after comparison?
+ // 1. deletion handling: recycler -> no, temp directory created only at first deletion
+ // 2. deletion handling: versioning -> "
+ // 3. log file creates containing folder -> no, log only created in batch mode, and only *before* comparison
+ }
+ }, procCallback); //may throw in error-callback!
+ return rv && !temporaryNetworkDrop;
+ }
+ }
+ return true;
+ };
+ if (!createDir(j->getBaseDirPf<LEFT_SIDE >(), j->wasExisting<LEFT_SIDE >()) ||
+ !createDir(j->getBaseDirPf<RIGHT_SIDE>(), j->wasExisting<RIGHT_SIDE>()))
+ continue; //skip this folder pair
+
//------------------------------------------------------------------------------------------
//execute synchronization recursively
//update synchronization database (automatic sync only)
- zen::ScopeGuard guardUpdateDb = zen::makeGuard([&]
+ ScopeGuard guardUpdateDb = makeGuard([&]
{
if (folderPairCfg.inAutomaticMode)
- try { zen::saveToDisk(*j); }
+ try { zen::saveLastSynchronousState(*j); }
catch (...) {} //throw FileError
});
@@ -2204,13 +2271,12 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf
#ifdef FFS_WIN
shadowCopyHandler.get(),
#endif
- delHandlerFp.first, delHandlerFp.second);
-
+ iterDelHandler->first, iterDelHandler->second);
syncFP.startSync(*j);
//(try to gracefully) cleanup temporary folders (Recycle bin optimization) -> will be done in ~DeletionHandling anyway...
- tryReportingError([&] { delHandlerFp.first .tryCleanup(); }, procCallback); //show error dialog if necessary
- tryReportingError([&] { delHandlerFp.second.tryCleanup(); }, procCallback); //
+ tryReportingError([&] { iterDelHandler->first .tryCleanup(); }, procCallback); //show error dialog if necessary
+ tryReportingError([&] { iterDelHandler->second.tryCleanup(); }, procCallback); //
//(try to gracefully) write database file (will be done in ~EnforceUpdateDatabase anyway...)
if (folderPairCfg.inAutomaticMode)
@@ -2218,7 +2284,7 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf
procCallback.reportStatus(_("Generating database..."));
procCallback.forceUiRefresh();
- tryReportingError([&] { zen::saveToDisk(*j); }, procCallback); //throw FileError
+ tryReportingError([&] { zen::saveLastSynchronousState(*j); }, procCallback); //throw FileError
guardUpdateDb.dismiss();
}
}
@@ -2277,7 +2343,7 @@ void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const
//start of (possibly) long-running copy process: ensure status updates are performed regularly
//in error situation: undo communication of processed amount of data
- zen::ScopeGuard guardStatistics = zen::makeGuard([&]
+ ScopeGuard guardStatistics = makeGuard([&]
{
procCallback_.updateProcessedData(0, -bytesReported);
bytesReported = 0;
@@ -2285,7 +2351,7 @@ void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const
WhileCopying<DelTargetCommand> callback(bytesReported, procCallback_, cmd);
- zen::copyFile(source, //type File implicitly means symlinks need to be dereferenced!
+ copyFile(source, //type File implicitly means symlinks need to be dereferenced!
target,
copyFilePermissions_,
transactionalFileCopy_,
@@ -2342,8 +2408,8 @@ void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const
//#################### Verification #############################
if (verifyCopiedFiles_)
{
- zen::ScopeGuard guardTarget = zen::makeGuard([&] { removeFile(target); }); //delete target if verification fails
- zen::ScopeGuard guardStatistics = zen::makeGuard([&]
+ ScopeGuard guardTarget = makeGuard([&] { removeFile(target); }); //delete target if verification fails
+ ScopeGuard guardStatistics = makeGuard([&]
{
procCallback_.updateProcessedData(0, -bytesReported);
bytesReported = 0;
bgstack15