From 8bf668665b107469086f16cb8ad23e47d479d2b4 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 17:14:37 +0200 Subject: 4.0 --- synchronization.cpp | 834 +++++++++++++++++++++++++++------------------------- 1 file changed, 441 insertions(+), 393 deletions(-) (limited to 'synchronization.cpp') diff --git a/synchronization.cpp b/synchronization.cpp index 297dcf51..bf96c2cb 100644 --- a/synchronization.cpp +++ b/synchronization.cpp @@ -5,9 +5,13 @@ // ************************************************************************** #include "synchronization.h" +#include +#include #include #include #include +#include +#include #include "shared/string_conv.h" #include "shared/util.h" #include "shared/loki/ScopeGuard.h" @@ -16,16 +20,12 @@ #include "shared/resolve_path.h" #include "shared/recycler.h" #include "shared/i18n.h" -#include -#include #include "shared/global_func.h" #include "shared/disable_standby.h" -#include #include "library/db_file.h" #include "library/dir_exist_async.h" #include "library/cmp_filetime.h" #include "shared/file_io.h" -#include #ifdef FFS_WIN #include "shared/long_path_prefix.h" @@ -53,7 +53,7 @@ void SyncStatistics::init() SyncStatistics::SyncStatistics(const FolderComparison& folderCmp) { init(); - std::for_each(folderCmp.begin(), folderCmp.end(), boost::bind(&SyncStatistics::getNumbersRecursively, this, _1)); + std::for_each(begin(folderCmp), end(folderCmp), [&](const BaseDirMapping& baseMap) { getNumbersRecursively(baseMap); }); } @@ -67,17 +67,14 @@ SyncStatistics::SyncStatistics(const HierarchyObject& hierObj) inline void SyncStatistics::getNumbersRecursively(const HierarchyObject& hierObj) { - //process directories std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(), - boost::bind(&SyncStatistics::getDirNumbers, this, _1)); + [&](const DirMapping& dirObj) { getDirNumbers(dirObj); }); - //process files std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(), - boost::bind(&SyncStatistics::getFileNumbers, this, _1)); + [&](const FileMapping& fileObj) { getFileNumbers(fileObj); }); - //process symlinks std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), - boost::bind(&SyncStatistics::getLinkNumbers, this, _1)); + [&](const SymLinkMapping& linkObj) { getLinkNumbers(linkObj); }); rowsTotal += hierObj.refSubDirs(). size(); rowsTotal += hierObj.refSubFiles().size(); @@ -245,17 +242,16 @@ std::vector zen::extractSyncCfg(const MainConfiguration& std::vector output; //process all pairs - for (std::vector::const_iterator i = allPairs.begin(); i != allPairs.end(); ++i) - output.push_back( - i->altSyncConfig.get() ? + for (auto i = allPairs.begin(); i != allPairs.end(); ++i) + { + SyncConfig syncCfg = i->altSyncConfig.get() ? *i->altSyncConfig : mainCfg.syncCfg; - FolderPairSyncCfg(i->altSyncConfig->syncConfiguration.var == SyncConfig::AUTOMATIC, - i->altSyncConfig->handleDeletion, - toZ(i->altSyncConfig->customDeletionDirectory)) : + output.push_back( + FolderPairSyncCfg(syncCfg.directionCfg.var == DirectionConfig::AUTOMATIC, + syncCfg.handleDeletion, + toZ(syncCfg.customDeletionDirectory))); + } - FolderPairSyncCfg(mainCfg.syncConfiguration.var == SyncConfig::AUTOMATIC, - mainCfg.handleDeletion, - toZ(mainCfg.customDeletionDirectory))); return output; } //------------------------------------------------------------------------------------------------------------ @@ -329,6 +325,18 @@ bool tryReportingError(ProcessCallback& handler, Function cmd) //return "true" o } +namespace +{ +template inline +S replaceCpy(const S& str, const T& old, const U& replacement, bool replaceAll = true) +{ + S tmp = str; + zen::replace(tmp, old, replacement, replaceAll); + return tmp; +} +} + + /* add some postfix to alternate deletion directory: deletionDirectory\2010-06-30 12-59-12\ */ @@ -341,8 +349,7 @@ Zstring getSessionDeletionDir(const Zstring& deletionDirectory, const Zstring& p if (!formattedDir.EndsWith(FILE_NAME_SEPARATOR)) formattedDir += FILE_NAME_SEPARATOR; - wxString timeNow = wxDateTime::Now().FormatISOTime(); - replace(timeNow, L":", L""); + const wxString timeNow = replaceCpy(wxDateTime::Now().FormatISOTime(), L":", L""); const wxString sessionName = wxDateTime::Now().FormatISODate() + wxChar(' ') + timeNow; formattedDir += prefix + toZ(sessionName); @@ -374,6 +381,10 @@ SyncProcess::SyncProcess(xmlAccess::OptionalDialogs& warnings, procCallback(handler) {} //-------------------------------------------------------------------------------------------------------------- +namespace +{ +struct CallbackRemoveDirImpl; +} class DeletionHandling //e.g. generate name of alternate deletion directory (unique for session AND folder pair) { @@ -387,17 +398,18 @@ public: //clean-up temporary directory (recycler bin optimization) void tryCleanup(); //throw FileError -> call this in non-exceptional coding, i.e. after Sync somewhere! - void removeFile (const Zstring& relativeName) const; //throw (FileError) - void removeFolder(const Zstring& relativeName) const; //throw (FileError) + void removeFile (const Zstring& relativeName) const; //throw FileError + void removeFolder(const Zstring& relativeName) const; //throw FileError - const Zstring& getTxtRemovingFile () const { return txtRemovingFile; } // - const Zstring& getTxtRemovingSymLink() const { return txtRemovingSymlink; } //status text templates - const Zstring& getTxtRemovingDir () const { return txtRemovingDirectory; }; // + const wxString& getTxtRemovingFile () const { return txtRemovingFile; } // + const wxString& getTxtRemovingSymLink() const { return txtRemovingSymlink; } //status text templates + const wxString& getTxtRemovingDir () const { return txtRemovingDirectory; } // //evaluate whether a deletion will actually free space within a volume bool deletionFreesSpace() const; private: + friend struct ::CallbackRemoveDirImpl; DeletionPolicy deletionType; ProcessCallback* procCallback_; //always bound! need assignment operator => not a reference @@ -406,9 +418,9 @@ private: Zstring baseDir_; //with separator postfix //preloaded status texts: - Zstring txtRemovingFile; - Zstring txtRemovingSymlink; - Zstring txtRemovingDirectory; + wxString txtRemovingFile; + wxString txtRemovingSymlink; + wxString txtRemovingDirectory; bool cleanedUp; }; @@ -424,32 +436,33 @@ DeletionHandling::DeletionHandling(DeletionPolicy handleDel, cleanedUp(false) { #ifdef FFS_WIN - if (deletionType == MOVE_TO_RECYCLE_BIN && recycleBinStatus(baseDir) != STATUS_REC_EXISTS) + if (baseDir.empty() || + (deletionType == MOVE_TO_RECYCLE_BIN && recycleBinStatus(baseDir) != STATUS_REC_EXISTS)) deletionType = DELETE_PERMANENTLY; //Windows' ::SHFileOperation() will do this anyway, but we have a better and faster deletion routine (e.g. on networks) #endif switch (deletionType) { case DELETE_PERMANENTLY: - txtRemovingFile = toZ(_("Deleting file %x")).Replace(Zstr("%x"), Zstr("\n\"%x\""), false); - txtRemovingSymlink = toZ(_("Deleting Symbolic Link %x")).Replace(Zstr("%x"), Zstr("\n\"%x\""), false); - txtRemovingDirectory = toZ(_("Deleting folder %x")).Replace( Zstr("%x"), Zstr("\n\"%x\""), false); + txtRemovingFile = replaceCpy(_("Deleting file %x" ), L"%x", L"\n\"%x\"", false); + txtRemovingDirectory = replaceCpy(_("Deleting folder %x" ), L"%x", L"\n\"%x\"", false); + txtRemovingSymlink = replaceCpy(_("Deleting symbolic link %x"), L"%x", L"\n\"%x\"", false); break; case MOVE_TO_RECYCLE_BIN: sessionDelDir = getSessionDeletionDir(baseDir_, Zstr("FFS ")); - txtRemovingFile = - txtRemovingSymlink = - txtRemovingDirectory = toZ(_("Moving %x to Recycle Bin")).Replace(Zstr("%x"), Zstr("\"%x\""), false); + txtRemovingFile = replaceCpy(_("Moving file %x to recycle bin" ), L"%x", L"\n\"%x\"", false); + txtRemovingDirectory = replaceCpy(_("Moving folder %x to recycle bin" ), L"%x", L"\n\"%x\"", false); + txtRemovingSymlink = replaceCpy(_("Moving symbolic link %x to recycle bin"), L"%x", L"\n\"%x\"", false); break; case MOVE_TO_CUSTOM_DIRECTORY: sessionDelDir = getSessionDeletionDir(custDelFolder); - txtRemovingFile = toZ(_("Moving file %x to user-defined directory %y")). Replace(Zstr("%x"), Zstr("\"%x\"\n"), false).Replace(Zstr("%y"), Zstring(Zstr("\"")) + custDelFolder + Zstr("\""), false); - txtRemovingDirectory = toZ(_("Moving folder %x to user-defined directory %y")). Replace(Zstr("%x"), Zstr("\"%x\"\n"), false).Replace(Zstr("%y"), Zstring(Zstr("\"")) + custDelFolder + Zstr("\""), false); - txtRemovingSymlink = toZ(_("Moving Symbolic Link %x to user-defined directory %y")).Replace(Zstr("%x"), Zstr("\"%x\"\n"), false).Replace(Zstr("%y"), Zstring(Zstr("\"")) + custDelFolder + Zstr("\""), false); + txtRemovingFile = replaceCpy(replaceCpy(_("Moving file %x to %y" ), L"%x", L"\n\"%x\"", false), L"%y", L"\"" + utf8CvrtTo(custDelFolder) + L"\"", false); + txtRemovingDirectory = replaceCpy(replaceCpy(_("Moving folder %x to %y" ), L"%x", L"\n\"%x\"", false), L"%y", L"\"" + utf8CvrtTo(custDelFolder) + L"\"", false); + txtRemovingSymlink = replaceCpy(replaceCpy(_("Moving symbolic link %x to %y"), L"%x", L"\n\"%x\"", false), L"%y", L"\"" + utf8CvrtTo(custDelFolder) + L"\"", false); break; } } @@ -494,15 +507,20 @@ private: struct CallbackRemoveDirImpl : public CallbackRemoveDir { - CallbackRemoveDirImpl(ProcessCallback& handler) : statusHandler_(handler) {} + CallbackRemoveDirImpl(const DeletionHandling& delHandling) : delHandling_(delHandling) {} - virtual void notifyDeletion(const Zstring& currentObject) + virtual void notifyFileDeletion(const Zstring& filename) { - statusHandler_.requestUiRefresh(); //exceptions may be thrown here! + delHandling_.procCallback_->reportStatus(replaceCpy(delHandling_.getTxtRemovingFile(), L"%x", utf8CvrtTo(filename))); + } + + virtual void notifyDirDeletion(const Zstring& dirname) + { + delHandling_.procCallback_->reportStatus(replaceCpy(delHandling_.getTxtRemovingDir(), L"%x", utf8CvrtTo(dirname))); } private: - ProcessCallback& statusHandler_; + const DeletionHandling& delHandling_; }; } @@ -525,17 +543,16 @@ void DeletionHandling::removeFile(const Zstring& relativeName) const try //rename file: no copying!!! { - if (!dirExistsUpdating(targetDir, *procCallback_)) + 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 //performance optimization!! Instead of moving each object into recycle bin separately, we rename them ony by one into a //temporary directory and delete this directory only ONCE! - renameFile(fullName, targetFile); //throw (FileError); + renameFile(fullName, targetFile); //throw FileError } - catch (...) + catch (FileError&) //if anything went wrong, move to recycle bin the standard way (single file processing: slow) { - //if anything went wrong, move to recycle bin the standard way (single file processing: slow) - moveToRecycleBin(fullName); //throw (FileError) + moveToRecycleBin(fullName); //throw FileError } } break; @@ -546,8 +563,8 @@ void DeletionHandling::removeFile(const Zstring& relativeName) const const Zstring targetFile = sessionDelDir + relativeName; //altDeletionDir ends with path separator const Zstring targetDir = targetFile.BeforeLast(FILE_NAME_SEPARATOR); - if (!dirExistsUpdating(targetDir, *procCallback_)) - createDirectory(targetDir); //throw (FileError) + if (!dirExists(targetDir)) + createDirectory(targetDir); //throw FileError CallbackMoveFileImpl callBack(*procCallback_); //if file needs to be copied we need callback functionality to update screen and offer abort moveFile(fullName, targetFile, true, &callBack); @@ -565,43 +582,41 @@ void DeletionHandling::removeFolder(const Zstring& relativeName) const { case DELETE_PERMANENTLY: { - CallbackRemoveDirImpl remDirCallback(*procCallback_); + CallbackRemoveDirImpl remDirCallback(*this); removeDirectory(fullName, &remDirCallback); } break; case MOVE_TO_RECYCLE_BIN: - if (dirExistsUpdating(fullName, *procCallback_)) + if (dirExists(fullName)) { const Zstring targetDir = sessionDelDir + relativeName; const Zstring targetSuperDir = targetDir.BeforeLast(FILE_NAME_SEPARATOR); try //rename directory: no copying!!! { - if (!dirExistsUpdating(targetSuperDir, *procCallback_)) + if (!dirExists(targetSuperDir)) createDirectory(targetSuperDir); //throw FileError -> may legitimately fail on Linux if permissions are missing - //performance optimization!! Instead of moving each object into recycle bin separately, we rename them ony by one into a + //performance optimization!! Instead of moving each object into recycle bin separately, we rename them one by one into a //temporary directory and delete this directory only ONCE! - renameFile(fullName, targetDir); //throw (FileError); + renameFile(fullName, targetDir); //throw FileError } - catch (...) + catch (FileError&) //if anything went wrong, move to recycle bin the standard way (single file processing: slow) { - //if anything went wrong, move to recycle bin the standard way (single file processing: slow) - moveToRecycleBin(fullName); //throw (FileError) - + moveToRecycleBin(fullName); //throw FileError } } break; case MOVE_TO_CUSTOM_DIRECTORY: - if (dirExistsUpdating(fullName, *procCallback_)) + if (dirExists(fullName)) { const Zstring targetDir = sessionDelDir + relativeName; const Zstring targetSuperDir = targetDir.BeforeLast(FILE_NAME_SEPARATOR); - if (!dirExistsUpdating(targetSuperDir, *procCallback_)) - createDirectory(targetSuperDir); //throw (FileError) + if (!dirExists(targetSuperDir)) + createDirectory(targetSuperDir); //throw FileError CallbackMoveFileImpl callBack(*procCallback_); //if files need to be copied, we need callback functionality to update screen and offer abort moveDirectory(fullName, targetDir, true, &callBack); @@ -659,7 +674,7 @@ private: //don't process directories //process files - for (HierarchyObject::SubFileMapping::const_iterator i = hierObj.refSubFiles().begin(); i != hierObj.refSubFiles().end(); ++i) + for (auto i = hierObj.refSubFiles().begin(); i != hierObj.refSubFiles().end(); ++i) switch (i->getSyncOperation()) //evaluate comparison result and sync direction { case SO_CREATE_NEW_LEFT: @@ -792,14 +807,13 @@ public: verifyCopiedFiles(syncProc.verifyCopiedFiles_), copyFilePermissions(syncProc.copyFilePermissions_), transactionalFileCopy(syncProc.transactionalFileCopy_), - txtCopyingFile (toZ(_("Copying new file %x to %y")). Replace(Zstr("%x"), Zstr("\"%x\""), false).Replace(Zstr("%y"), Zstr("\n\"%y\""), false)), - txtCopyingLink (toZ(_("Copying new Symbolic Link %x to %y")).Replace(Zstr("%x"), Zstr("\"%x\""), false).Replace(Zstr("%y"), Zstr("\n\"%y\""), false)), - txtOverwritingFile(toZ(_("Overwriting file %x in %y")). Replace(Zstr("%x"), Zstr("\"%x\""), false).Replace(Zstr("%y"), Zstr("\n\"%y\""), false)), - txtOverwritingLink(toZ(_("Overwriting Symbolic Link %x in %y")).Replace(Zstr("%x"), Zstr("\"%x\""), false).Replace(Zstr("%y"), Zstr("\n\"%y\""), false)), - txtCreatingFolder (toZ(_("Creating folder %x")).Replace(Zstr("%x"), Zstr("\n\"%x\""), false)), - txtVerifying (toZ(_("Verifying file %x")). Replace(Zstr("%x"), Zstr("\n\"%x\""), false)), - txtWritingAttributes(toZ(_("Updating attributes of %x")).Replace(Zstr("%x"), Zstr("\n\"%x\""), false)) {} - + txtCreatingFile (replaceCpy(_("Creating file %x" ), L"%x", L"\n\"%x\"", false)), + txtCreatingLink (replaceCpy(_("Creating symbolic link %x" ), L"%x", L"\n\"%x\"", false)), + txtCreatingFolder (replaceCpy(_("Creating folder %x" ), L"%x", L"\n\"%x\"", false)), + txtOverwritingFile (replaceCpy(_("Overwriting file %x" ), L"%x", L"\n\"%x\"", false)), + txtOverwritingLink (replaceCpy(_("Overwriting symbolic link %x"), L"%x", L"\n\"%x\"", false)), + txtVerifying (replaceCpy(_("Verifying file %x" ), L"%x", L"\n\"%x\"", false)), + txtWritingAttributes(replaceCpy(_("Updating attributes of %x" ), L"%x", L"\n\"%x\"", false)) {} void startSync(BaseDirMapping& baseMap) { @@ -825,9 +839,8 @@ private: //more low level helper template void deleteSymlink(const SymLinkMapping& linkObj) const; - void copySymlink(const Zstring& source, const Zstring& target, LinkDescriptor::LinkType type, bool inRecursion = false) const; - template - void copyFileUpdating(const Zstring& source, const Zstring& target, const DelTargetCommand& cmd, zen::UInt64 sourceFileSize, int recursionLvl = 0) const; + template + void copyFileUpdatingTo(const FileMapping& fileObj, const DelTargetCommand& cmd, FileDescriptor& sourceAttr) const; void verifyFileCopy(const Zstring& source, const Zstring& target) const; ProcessCallback& procCallback_; @@ -842,13 +855,13 @@ private: const bool transactionalFileCopy; //preload status texts - const Zstring txtCopyingFile; - const Zstring txtCopyingLink; - const Zstring txtOverwritingFile; - const Zstring txtOverwritingLink; - const Zstring txtCreatingFolder; - const Zstring txtVerifying; - const Zstring txtWritingAttributes; + const wxString txtCreatingFile; + const wxString txtCreatingLink; + const wxString txtCreatingFolder; + const wxString txtOverwritingFile; + const wxString txtOverwritingLink; + const wxString txtVerifying; + const wxString txtWritingAttributes; }; @@ -885,7 +898,7 @@ void SynchronizeFolderPair::execute(HierarchyObject& hierObj) void SynchronizeFolderPair::synchronizeFile(FileMapping& fileObj) const { - Zstring statusText; + wxString logText; Zstring target; switch (fileObj.getSyncOperation()) //evaluate comparison result and sync direction @@ -893,103 +906,146 @@ void SynchronizeFolderPair::synchronizeFile(FileMapping& fileObj) const case SO_CREATE_NEW_LEFT: target = fileObj.getBaseDirPf() + fileObj.getRelativeName(); //can't use "getFullName" as target is not yet existing - statusText = txtCopyingFile; - statusText.Replace(Zstr("%x"), fileObj.getShortName(), false); - statusText.Replace(Zstr("%y"), target.BeforeLast(FILE_NAME_SEPARATOR), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = txtCreatingFile; + replace(logText, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); - copyFileUpdating(fileObj.getFullName(), target, - []() {}, //no target to delete - fileObj.getFileSize()); + try + { + FileDescriptor sourceAttr; + copyFileUpdatingTo(fileObj, + []() {}, //no target to delete + sourceAttr); + + fileObj.copyTo(&sourceAttr); //update FileMapping + } + catch (FileError&) + { + if (fileExists(fileObj.getFullName())) + throw; + //source deleted meanwhile... + procCallback_.updateProcessedData(0, to(fileObj.getFileSize())); + fileObj.removeObject(); + } break; case SO_CREATE_NEW_RIGHT: target = fileObj.getBaseDirPf() + fileObj.getRelativeName(); - statusText = txtCopyingFile; - statusText.Replace(Zstr("%x"), fileObj.getShortName(), false); - statusText.Replace(Zstr("%y"), target.BeforeLast(FILE_NAME_SEPARATOR), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = txtCreatingFile; + replace(logText, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); + + try + { + FileDescriptor sourceAttr; + copyFileUpdatingTo(fileObj, + []() {}, //no target to delete + sourceAttr); - copyFileUpdating(fileObj.getFullName(), target, - []() {}, //no target to delete - fileObj.getFileSize()); + fileObj.copyTo(&sourceAttr); //update FileMapping + } + catch (FileError&) + { + if (fileExists(fileObj.getFullName())) + throw; + //source deleted meanwhile... + procCallback_.updateProcessedData(0, to(fileObj.getFileSize())); + fileObj.removeObject(); + } break; case SO_DELETE_LEFT: - statusText = delHandlingLeft_.getTxtRemovingFile(); - statusText.Replace(Zstr("%x"), fileObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(delHandlingLeft_.getTxtRemovingFile(), L"%x", utf8CvrtTo(fileObj.getFullName())); + procCallback_.reportInfo(logText); - delHandlingLeft_.removeFile(fileObj.getObjRelativeName()); //throw (FileError) + delHandlingLeft_.removeFile(fileObj.getObjRelativeName()); //throw FileError + fileObj.removeObject(); //update FileMapping break; case SO_DELETE_RIGHT: - statusText = delHandlingRight_.getTxtRemovingFile(); - statusText.Replace(Zstr("%x"), fileObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(delHandlingRight_.getTxtRemovingFile(), L"%x", utf8CvrtTo(fileObj.getFullName())); + procCallback_.reportInfo(logText); - delHandlingRight_.removeFile(fileObj.getObjRelativeName()); //throw (FileError) + delHandlingRight_.removeFile(fileObj.getObjRelativeName()); //throw FileError + fileObj.removeObject(); //update FileMapping break; case SO_OVERWRITE_LEFT: + { target = fileObj.getBaseDirPf() + fileObj.getRelativeName(); //respect differences in case of source object - statusText = txtOverwritingFile; - statusText.Replace(Zstr("%x"), fileObj.getShortName(), false); - statusText.Replace(Zstr("%y"), fileObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = txtOverwritingFile; + replace(logText, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); - copyFileUpdating(fileObj.getFullName(), target, - [&]() //delete target at appropriate time + FileDescriptor sourceAttr; + copyFileUpdatingTo(fileObj, + [&]() //delete target at appropriate time { - delHandlingLeft_.removeFile(fileObj.getObjRelativeName()); //throw (FileError) + procCallback_.reportStatus(replaceCpy(delHandlingLeft_.getTxtRemovingFile(), L"%x", utf8CvrtTo(fileObj.getFullName()))); + + delHandlingLeft_.removeFile(fileObj.getObjRelativeName()); //throw FileError fileObj.removeObject(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) - }, - fileObj.getFileSize()); - break; + + procCallback_.reportStatus(logText); //restore status text copy file + }, sourceAttr); + + fileObj.copyTo(&sourceAttr); //update FileMapping + } + break; case SO_OVERWRITE_RIGHT: + { target = fileObj.getBaseDirPf() + fileObj.getRelativeName(); //respect differences in case of source object - statusText = txtOverwritingFile; - statusText.Replace(Zstr("%x"), fileObj.getShortName(), false); - statusText.Replace(Zstr("%y"), fileObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = txtOverwritingFile; + replace(logText, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); - copyFileUpdating(fileObj.getFullName(), target, - [&]() //delete target at appropriate time + FileDescriptor sourceAttr; + copyFileUpdatingTo(fileObj, + [&]() //delete target at appropriate time { - delHandlingRight_.removeFile(fileObj.getObjRelativeName()); //throw (FileError) + procCallback_.reportStatus(replaceCpy(delHandlingRight_.getTxtRemovingFile(), L"%x", utf8CvrtTo(fileObj.getFullName()))); + + delHandlingRight_.removeFile(fileObj.getObjRelativeName()); //throw FileError fileObj.removeObject(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) - }, - fileObj.getFileSize()); - break; + + procCallback_.reportStatus(logText); //restore status text copy file + }, sourceAttr); + + fileObj.copyTo(&sourceAttr); //update FileMapping + } + break; case SO_COPY_METADATA_TO_LEFT: - statusText = txtWritingAttributes; - statusText.Replace(Zstr("%x"), fileObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo(fileObj.getFullName())); + procCallback_.reportInfo(logText); if (fileObj.getShortName() != fileObj.getShortName()) //adapt difference in case (windows only) renameFile(fileObj.getFullName(), - fileObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName()); //throw (FileError); + fileObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName()); //throw FileError; - if (!sameFileTime(fileObj.getLastWriteTime(), fileObj.getLastWriteTime(), 2)) ////respect 2 second FAT/FAT32 precision - copyFileTimes(fileObj.getFullName(), fileObj.getFullName(), true); //deref symlinks; throw (FileError) + if (!sameFileTime(fileObj.getLastWriteTime(), fileObj.getLastWriteTime(), 2)) //respect 2 second FAT/FAT32 precision + setFileTime(fileObj.getFullName(), fileObj.getLastWriteTime(), SYMLINK_FOLLOW); //throw FileError + //do NOT read *current* source file time, but use buffered value which corresponds to time of comparison! + + fileObj.copyTo(NULL); //-> both sides *should* be completely equal now... break; case SO_COPY_METADATA_TO_RIGHT: - statusText = txtWritingAttributes; - statusText.Replace(Zstr("%x"), fileObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo(fileObj.getFullName())); + procCallback_.reportInfo(logText); if (fileObj.getShortName() != fileObj.getShortName()) //adapt difference in case (windows only) renameFile(fileObj.getFullName(), - fileObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName()); //throw (FileError); + fileObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName()); //throw FileError; + + if (!sameFileTime(fileObj.getLastWriteTime(), fileObj.getLastWriteTime(), 2)) //respect 2 second FAT/FAT32 precision + setFileTime(fileObj.getFullName(), fileObj.getLastWriteTime(), SYMLINK_FOLLOW); //throw FileError - if (!sameFileTime(fileObj.getLastWriteTime(), fileObj.getLastWriteTime(), 2)) ////respect 2 second FAT/FAT32 precision - copyFileTimes(fileObj.getFullName(), fileObj.getFullName(), true); //deref symlinks; throw (FileError) + fileObj.copyTo(NULL); //-> both sides *should* be completely equal now... break; case SO_DO_NOTHING: @@ -998,9 +1054,6 @@ void SynchronizeFolderPair::synchronizeFile(FileMapping& fileObj) const return; //no update on processed data! } - //update FileMapping - fileObj.synchronizeSides(); - //progress indicator update //indicator is updated only if file is sync'ed correctly (and if some sync was done)! procCallback_.updateProcessedData(1, 0); //processed data is communicated in subfunctions! @@ -1010,7 +1063,7 @@ void SynchronizeFolderPair::synchronizeFile(FileMapping& fileObj) const void SynchronizeFolderPair::synchronizeLink(SymLinkMapping& linkObj) const { - Zstring statusText; + wxString logText; Zstring target; switch (linkObj.getSyncOperation()) //evaluate comparison result and sync direction @@ -1018,93 +1071,125 @@ void SynchronizeFolderPair::synchronizeLink(SymLinkMapping& linkObj) const case SO_CREATE_NEW_LEFT: target = linkObj.getBaseDirPf() + linkObj.getRelativeName(); - statusText = txtCopyingLink; - statusText.Replace(Zstr("%x"), linkObj.getShortName(), false); - statusText.Replace(Zstr("%y"), target.BeforeLast(FILE_NAME_SEPARATOR), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = txtCreatingLink; + replace(logText, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); + + try + { + zen::copySymlink(linkObj.getFullName(), target, copyFilePermissions); //throw FileError - copySymlink(linkObj.getFullName(), target, linkObj.getLinkType()); + linkObj.copyTo(); //update SymLinkMapping + } + catch (FileError&) + { + if (fileExists(linkObj.getFullName())) + throw; + //source deleted meanwhile... + linkObj.removeObject(); + } break; case SO_CREATE_NEW_RIGHT: target = linkObj.getBaseDirPf() + linkObj.getRelativeName(); - statusText = txtCopyingLink; - statusText.Replace(Zstr("%x"), linkObj.getShortName(), false); - statusText.Replace(Zstr("%y"), target.BeforeLast(FILE_NAME_SEPARATOR), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = txtCreatingLink; + replace(logText, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); + + try + { + zen::copySymlink(linkObj.getFullName(), target, copyFilePermissions); //throw FileError - copySymlink(linkObj.getFullName(), target, linkObj.getLinkType()); + linkObj.copyTo(); //update SymLinkMapping + } + catch (FileError&) + { + if (fileExists(linkObj.getFullName())) + throw; + //source deleted meanwhile... + linkObj.removeObject(); + } break; case SO_DELETE_LEFT: - statusText = delHandlingLeft_.getTxtRemovingSymLink(); - statusText.Replace(Zstr("%x"), linkObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(delHandlingLeft_.getTxtRemovingSymLink(), L"%x", utf8CvrtTo(linkObj.getFullName())); + procCallback_.reportInfo(logText); + + deleteSymlink(linkObj); //throw FileError - deleteSymlink(linkObj); //throw (FileError) + linkObj.removeObject(); //update SymLinkMapping break; case SO_DELETE_RIGHT: - statusText = delHandlingRight_.getTxtRemovingSymLink(); - statusText.Replace(Zstr("%x"), linkObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(delHandlingRight_.getTxtRemovingSymLink(), L"%x", utf8CvrtTo(linkObj.getFullName())); + procCallback_.reportInfo(logText); - deleteSymlink(linkObj); //throw (FileError) + deleteSymlink(linkObj); //throw FileError + + linkObj.removeObject(); //update SymLinkMapping break; case SO_OVERWRITE_LEFT: target = linkObj.getBaseDirPf() + linkObj.getRelativeName(); //respect differences in case of source object - statusText = txtOverwritingLink; - statusText.Replace(Zstr("%x"), linkObj.getShortName(), false); - statusText.Replace(Zstr("%y"), linkObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = txtOverwritingLink; + replace(logText, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); - deleteSymlink(linkObj); //throw (FileError) + procCallback_.reportStatus(replaceCpy(delHandlingLeft_.getTxtRemovingSymLink(), L"%x", utf8CvrtTo(linkObj.getFullName()))); + deleteSymlink(linkObj); //throw FileError linkObj.removeObject(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) - copySymlink(linkObj.getFullName(), target, linkObj.getLinkType()); + procCallback_.reportStatus(logText); //restore status text + zen::copySymlink(linkObj.getFullName(), target, copyFilePermissions); //throw FileError + + linkObj.copyTo(); //update SymLinkMapping break; case SO_OVERWRITE_RIGHT: target = linkObj.getBaseDirPf() + linkObj.getRelativeName(); //respect differences in case of source object - statusText = txtOverwritingLink; - statusText.Replace(Zstr("%x"), linkObj.getShortName(), false); - statusText.Replace(Zstr("%y"), linkObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = txtOverwritingLink; + replace(logText, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); - deleteSymlink(linkObj); //throw (FileError) + procCallback_.reportStatus(replaceCpy(delHandlingRight_.getTxtRemovingSymLink(), L"%x", utf8CvrtTo(linkObj.getFullName()))); + deleteSymlink(linkObj); //throw FileError linkObj.removeObject(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) - copySymlink(linkObj.getFullName(), target, linkObj.getLinkType()); + procCallback_.reportStatus(logText); //restore status text + zen::copySymlink(linkObj.getFullName(), target, copyFilePermissions); //throw FileError + + linkObj.copyTo(); //update SymLinkMapping break; case SO_COPY_METADATA_TO_LEFT: - statusText = txtWritingAttributes; - statusText.Replace(Zstr("%x"), linkObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo(linkObj.getFullName())); + procCallback_.reportInfo(logText); if (linkObj.getShortName() != linkObj.getShortName()) //adapt difference in case (windows only) renameFile(linkObj.getFullName(), - linkObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + linkObj.getShortName()); //throw (FileError); + linkObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + linkObj.getShortName()); //throw FileError; + + if (!sameFileTime(linkObj.getLastWriteTime(), linkObj.getLastWriteTime(), 2)) //respect 2 second FAT/FAT32 precision + setFileTime(linkObj.getFullName(), linkObj.getLastWriteTime(), SYMLINK_DIRECT); //throw FileError - if (!sameFileTime(linkObj.getLastWriteTime(), linkObj.getLastWriteTime(), 2)) ////respect 2 second FAT/FAT32 precision - copyFileTimes(linkObj.getFullName(), linkObj.getFullName(), false); //don't deref symlinks; throw (FileError) + linkObj.copyTo(); //-> both sides *should* be completely equal now... break; case SO_COPY_METADATA_TO_RIGHT: - statusText = txtWritingAttributes; - statusText.Replace(Zstr("%x"), linkObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo(linkObj.getFullName())); + procCallback_.reportInfo(logText); if (linkObj.getShortName() != linkObj.getShortName()) //adapt difference in case (windows only) renameFile(linkObj.getFullName(), - linkObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + linkObj.getShortName()); //throw (FileError); + linkObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + linkObj.getShortName()); //throw FileError; - if (!sameFileTime(linkObj.getLastWriteTime(), linkObj.getLastWriteTime(), 2)) ////respect 2 second FAT/FAT32 precision - copyFileTimes(linkObj.getFullName(), linkObj.getFullName(), false); //don't deref symlinks; throw (FileError) + if (!sameFileTime(linkObj.getLastWriteTime(), linkObj.getLastWriteTime(), 2)) //respect 2 second FAT/FAT32 precision + setFileTime(linkObj.getFullName(), linkObj.getLastWriteTime(), SYMLINK_DIRECT); //throw FileError + + linkObj.copyTo(); //-> both sides *should* be completely equal now... break; case SO_DO_NOTHING: @@ -1113,9 +1198,6 @@ void SynchronizeFolderPair::synchronizeLink(SymLinkMapping& linkObj) const return; //no update on processed data! } - //update FileMapping - linkObj.synchronizeSides(); - //progress indicator update //indicator is updated only if file is sync'ed correctly (and if some sync was done)! procCallback_.updateProcessedData(1, 0); //processed data is communicated in subfunctions! @@ -1125,67 +1207,92 @@ void SynchronizeFolderPair::synchronizeLink(SymLinkMapping& linkObj) const void SynchronizeFolderPair::synchronizeFolder(DirMapping& dirObj) const { - Zstring statusText; + wxString logText; Zstring target; //synchronize folders: switch (dirObj.getSyncOperation()) //evaluate comparison result and sync direction { case SO_CREATE_NEW_LEFT: - target = dirObj.getBaseDirPf() + dirObj.getRelativeName(); + //some check to catch the error that directory on source has been deleted externally after "compare"... + if (!dirExists(dirObj.getFullName())) + { + // throw FileError(_ ("Source directory does not exist anymore:") + "\n\"" + dirObj.getFullName() + "\""); + const SyncStatistics subObjects(dirObj); //DON'T forget to notify about implicitly deleted objects! + procCallback_.updateProcessedData(subObjects.getCreate() + subObjects.getOverwrite() + subObjects.getDelete(), to(subObjects.getDataToProcess())); + + dirObj.refSubFiles().clear(); //...then remove sub-objects + dirObj.refSubLinks().clear(); // + dirObj.refSubDirs ().clear(); // + dirObj.removeObject(); + } + else + { + target = dirObj.getBaseDirPf() + dirObj.getRelativeName(); - statusText = txtCreatingFolder; - statusText.Replace(Zstr("%x"), target, false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(txtCreatingFolder, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); - //some check to catch the error that directory on source has been deleted externally after "compare"... - if (!dirExistsUpdating(dirObj.getFullName(), procCallback_)) - throw FileError(_("Source directory does not exist anymore:") + "\n\"" + dirObj.getFullName() + "\""); - createDirectory(target, dirObj.getFullName(), copyFilePermissions); //no symlink copying! + createDirectory(target, dirObj.getFullName(), copyFilePermissions); //no symlink copying! + dirObj.copyTo(); //update DirMapping + } break; case SO_CREATE_NEW_RIGHT: - target = dirObj.getBaseDirPf() + dirObj.getRelativeName(); + //some check to catch the error that directory on source has been deleted externally after "compare"... + if (!dirExists(dirObj.getFullName())) + { + //throw FileError(_ ("Source directory does not exist anymore:") + "\n\"" + dirObj.getFullName() + "\""); + const SyncStatistics subObjects(dirObj); //DON'T forget to notify about implicitly deleted objects! + procCallback_.updateProcessedData(subObjects.getCreate() + subObjects.getOverwrite() + subObjects.getDelete(), to(subObjects.getDataToProcess())); - statusText = txtCreatingFolder; - statusText.Replace(Zstr("%x"), target, false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + dirObj.refSubFiles().clear(); //...then remove sub-objects + dirObj.refSubLinks().clear(); // + dirObj.refSubDirs ().clear(); // + dirObj.removeObject(); + } + else + { + target = dirObj.getBaseDirPf() + dirObj.getRelativeName(); - //some check to catch the error that directory on source has been deleted externally after "compare"... - if (!dirExistsUpdating(dirObj.getFullName(), procCallback_)) - throw FileError(_("Source directory does not exist anymore:") + "\n\"" + dirObj.getFullName() + "\""); - createDirectory(target, dirObj.getFullName(), copyFilePermissions); //no symlink copying! + logText = replaceCpy(txtCreatingFolder, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); + + createDirectory(target, dirObj.getFullName(), copyFilePermissions); //no symlink copying! + dirObj.copyTo(); //update DirMapping + } break; case SO_COPY_METADATA_TO_LEFT: - statusText = txtWritingAttributes; - statusText.Replace(Zstr("%x"), dirObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo(dirObj.getFullName())); + procCallback_.reportInfo(logText); if (dirObj.getShortName() != dirObj.getShortName()) //adapt difference in case (windows only) renameFile(dirObj.getFullName(), - dirObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + dirObj.getShortName()); //throw (FileError); - //copyFileTimes(dirObj.getFullName(), dirObj.getFullName(), true); //throw (FileError) -> is executed after sub-objects have finished synchronization + dirObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + dirObj.getShortName()); //throw FileError; + //copyFileTimes(dirObj.getFullName(), dirObj.getFullName(), true); //throw FileError -> is executed after sub-objects have finished synchronization + + dirObj.copyTo(); //-> both sides *should* be completely equal now... break; case SO_COPY_METADATA_TO_RIGHT: - statusText = txtWritingAttributes; - statusText.Replace(Zstr("%x"), dirObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo(dirObj.getFullName())); + procCallback_.reportInfo(logText); if (dirObj.getShortName() != dirObj.getShortName()) //adapt difference in case (windows only) renameFile(dirObj.getFullName(), - dirObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + dirObj.getShortName()); //throw (FileError); - //copyFileTimes(dirObj.getFullName(), dirObj.getFullName(), true); //throw (FileError) -> is executed after sub-objects have finished synchronization + dirObj.getFullName().BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + dirObj.getShortName()); //throw FileError; + //copyFileTimes(dirObj.getFullName(), dirObj.getFullName(), true); //throw FileError -> is executed after sub-objects have finished synchronization + + dirObj.copyTo(); //-> both sides *should* be completely equal now... break; case SO_DELETE_LEFT: //status information - statusText = delHandlingLeft_.getTxtRemovingDir(); - statusText.Replace(Zstr("%x"), dirObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(delHandlingLeft_.getTxtRemovingDir(), L"%x", utf8CvrtTo(dirObj.getFullName())); + procCallback_.reportInfo(logText); - delHandlingLeft_.removeFolder(dirObj.getObjRelativeName()); //throw (FileError) + delHandlingLeft_.removeFolder(dirObj.getObjRelativeName()); //throw FileError { //progress indicator update: DON'T forget to notify about implicitly deleted objects! const SyncStatistics subObjects(dirObj); @@ -1195,15 +1302,15 @@ void SynchronizeFolderPair::synchronizeFolder(DirMapping& dirObj) const dirObj.refSubDirs ().clear(); procCallback_.updateProcessedData(subObjects.getCreate() + subObjects.getOverwrite() + subObjects.getDelete(), to(subObjects.getDataToProcess())); } + dirObj.removeObject(); //update DirMapping break; case SO_DELETE_RIGHT: //status information - statusText = delHandlingRight_.getTxtRemovingDir(); - statusText.Replace(Zstr("%x"), dirObj.getFullName(), false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + logText = replaceCpy(delHandlingRight_.getTxtRemovingDir(), L"%x", utf8CvrtTo(dirObj.getFullName())); + procCallback_.reportInfo(logText); - delHandlingRight_.removeFolder(dirObj.getObjRelativeName()); //throw (FileError) + delHandlingRight_.removeFolder(dirObj.getObjRelativeName()); //throw FileError { //progress indicator update: DON'T forget to notify about implicitly deleted objects! const SyncStatistics subObjects(dirObj); @@ -1213,6 +1320,7 @@ void SynchronizeFolderPair::synchronizeFolder(DirMapping& dirObj) const dirObj.refSubDirs ().clear(); procCallback_.updateProcessedData(subObjects.getCreate() + subObjects.getOverwrite() + subObjects.getDelete(), to(subObjects.getDataToProcess())); } + dirObj.removeObject(); //update DirMapping break; case SO_OVERWRITE_RIGHT: @@ -1224,9 +1332,6 @@ void SynchronizeFolderPair::synchronizeFolder(DirMapping& dirObj) const return; //no update on processed data! } - //update DirMapping - dirObj.synchronizeSides(); - //progress indicator update //indicator is updated only if directory is sync'ed correctly (and if some work was done)! procCallback_.updateProcessedData(1, 0); //each call represents one processed file @@ -1234,15 +1339,6 @@ void SynchronizeFolderPair::synchronizeFolder(DirMapping& dirObj) const } -//avoid data loss when source directory doesn't (temporarily?) exist anymore AND user chose to ignore errors (else we wouldn't arrive here) -bool dataLossPossible(const Zstring& dirName, const SyncStatistics& folderPairStat, ProcessCallback& procCallback) -{ - return folderPairStat.getCreate() + folderPairStat.getOverwrite() + folderPairStat.getConflict() == 0 && - folderPairStat.getDelete() > 0 && //deletions only... (respect filtered items!) - !dirName.empty() && !dirExistsUpdating(dirName, procCallback); -} - - namespace { void makeSameLength(std::wstring& first, std::wstring& second) @@ -1323,28 +1419,25 @@ void SyncProcess::startSynchronizationProcess(const std::vector(statisticsTotal.getDataToProcess()), ProcessCallback::PROCESS_SYNCHRONIZING); - if (!synchronizationNeeded(statisticsTotal)) - procCallback.reportInfo(_("Nothing to synchronize according to configuration!")); //inform about this special case - std::deque skipFolderPair(folderCmp.size()); //folder pairs may be skipped after fatal errors were found //initialize deletion handling: already required when checking for warnings std::vector> delHandler; - for (FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + for (auto j = begin(folderCmp); j != end(folderCmp); ++j) { const size_t folderIndex = j - folderCmp.begin(); const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex]; delHandler.push_back(std::make_pair(DeletionHandling(folderPairCfg.handleDeletion, folderPairCfg.custDelFolder, - j->getBaseDir(), + j->getBaseDirPf(), procCallback), DeletionHandling(folderPairCfg.handleDeletion, folderPairCfg.custDelFolder, - j->getBaseDir(), + j->getBaseDirPf(), procCallback))); } @@ -1367,27 +1460,27 @@ void SyncProcess::startSynchronizationProcess(const std::vectorgetBaseDir(), j->getBaseDir())) + if (EqualFilename()(j->getBaseDirPf(), j->getBaseDirPf())) continue; const FolderPairSyncCfg& folderPairCfg = syncConfig[folderIndex]; const std::pair& delHandlerFp = delHandler[folderIndex]; - const SyncStatistics statisticsFolderPair(*j); + const SyncStatistics folderPairStat(*j); //aggregate basic information - const bool writeLeft = statisticsFolderPair.getCreate () + - statisticsFolderPair.getOverwrite() + - statisticsFolderPair.getDelete () > 0; + const bool writeLeft = folderPairStat.getCreate () + + folderPairStat.getOverwrite() + + folderPairStat.getDelete () > 0; - const bool writeRight = statisticsFolderPair.getCreate () + - statisticsFolderPair.getOverwrite() + - statisticsFolderPair.getDelete () > 0; + const bool writeRight = folderPairStat.getCreate () + + folderPairStat.getOverwrite() + + folderPairStat.getDelete () > 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 && @@ -1399,8 +1492,8 @@ void SyncProcess::startSynchronizationProcess(const std::vectorgetBaseDir(). empty() && (writeLeft || folderPairCfg.inAutomaticMode)) || - (j->getBaseDir().empty() && (writeRight || folderPairCfg.inAutomaticMode))) + if ((j->getBaseDirPf(). empty() && (writeLeft || folderPairCfg.inAutomaticMode)) || + (j->getBaseDirPf().empty() && (writeRight || folderPairCfg.inAutomaticMode))) { procCallback.reportFatalError(_("Target directory name must not be empty!")); skipFolderPair[folderIndex] = true; @@ -1408,30 +1501,30 @@ void SyncProcess::startSynchronizationProcess(const std::vectorgetBaseDir(), j->getBaseDir())) //true in general + if (!EqualDependentDirectory()(j->getBaseDirPf(), j->getBaseDirPf())) //true in general { if (writeLeft) { - ++dirWriteCount[j->getBaseDir()]; + ++dirWriteCount[j->getBaseDirPf()]; if (writeRight) - ++dirWriteCount[j->getBaseDir()]; + ++dirWriteCount[j->getBaseDirPf()]; else - dirReadCount.insert(j->getBaseDir()); + dirReadCount.insert(j->getBaseDirPf()); } else if (writeRight) { - dirReadCount.insert(j->getBaseDir()); - ++dirWriteCount[j->getBaseDir()]; + dirReadCount.insert(j->getBaseDirPf()); + ++dirWriteCount[j->getBaseDirPf()]; } } 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) - ++dirWriteCount[j->getBaseDir()]; + ++dirWriteCount[j->getBaseDirPf()]; } - if (statisticsFolderPair.getOverwrite() + statisticsFolderPair.getDelete() > 0) + if (folderPairStat.getOverwrite() + folderPairStat.getDelete() > 0) { if (folderPairCfg.handleDeletion == zen::MOVE_TO_CUSTOM_DIRECTORY) { @@ -1445,61 +1538,64 @@ void SyncProcess::startSynchronizationProcess(const std::vectorgetBaseDir(), statisticsFolderPair, procCallback)) - { - procCallback.reportFatalError(_("Source directory does not exist anymore:") + "\n\"" + j->getBaseDir() + "\""); - skipFolderPair[folderIndex] = true; - continue; - } - if (dataLossPossible(j->getBaseDir(), statisticsFolderPair, procCallback)) + //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.getOverwrite() + folderPairStat.getConflict() == 0 && + folderPairStat.getDelete() > 0) //deletions only... (respect filtered items!) { - procCallback.reportFatalError(_("Source directory does not exist anymore:") + "\n\"" + j->getBaseDir() + "\""); - skipFolderPair[folderIndex] = true; - continue; + Zstring missingSrcDir; + if (!j->getBaseDirPf().empty() && !j->wasExisting()) //important: we need to evaluate existence status from time of comparison! + missingSrcDir = j->getBaseDirPf(); + if (!j->getBaseDirPf().empty() && !j->wasExisting()) + missingSrcDir = j->getBaseDirPf(); + + if (!missingSrcDir.empty()) + { + procCallback.reportFatalError(_("Source directory does not exist anymore:") + "\n\"" + missingSrcDir + "\""); + skipFolderPair[folderIndex] = true; + continue; + } } //check if more than 50% of total number of files/dirs are to be created/overwritten/deleted - if (significantDifferenceDetected(statisticsFolderPair)) - significantDiff.push_back(std::make_pair(j->getBaseDir(), j->getBaseDir())); + if (significantDifferenceDetected(folderPairStat)) + significantDiff.push_back(std::make_pair(j->getBaseDirPf(), j->getBaseDirPf())); //check for sufficient free diskspace in left directory const std::pair spaceNeeded = DiskSpaceNeeded(*j, delHandlerFp.first.deletionFreesSpace(), delHandlerFp.second.deletionFreesSpace()).getSpaceTotal(); - wxLongLong freeDiskSpaceLeft; - if (wxGetDiskSpace(toWx(j->getBaseDir()), NULL, &freeDiskSpaceLeft)) + if (wxGetDiskSpace(toWx(j->getBaseDirPf()), NULL, &freeDiskSpaceLeft)) { if (0 < freeDiskSpaceLeft && //zero disk space is either an error or not: in both cases this warning message is obsolete (WebDav seems to report 0) freeDiskSpaceLeft.ToDouble() < to(spaceNeeded.first)) - diskSpaceMissing.push_back(std::make_pair(j->getBaseDir(), std::make_pair(spaceNeeded.first, freeDiskSpaceLeft.ToDouble()))); + diskSpaceMissing.push_back(std::make_pair(j->getBaseDirPf(), std::make_pair(spaceNeeded.first, freeDiskSpaceLeft.ToDouble()))); } //check for sufficient free diskspace in right directory wxLongLong freeDiskSpaceRight; - if (wxGetDiskSpace(toWx(j->getBaseDir()), NULL, &freeDiskSpaceRight)) + if (wxGetDiskSpace(toWx(j->getBaseDirPf()), NULL, &freeDiskSpaceRight)) { if (0 < freeDiskSpaceRight && //zero disk space is either an error or not: in both cases this warning message is obsolete (WebDav seems to report 0) freeDiskSpaceRight.ToDouble() < to(spaceNeeded.second)) - diskSpaceMissing.push_back(std::make_pair(j->getBaseDir(), std::make_pair(spaceNeeded.second, freeDiskSpaceRight.ToDouble()))); + diskSpaceMissing.push_back(std::make_pair(j->getBaseDirPf(), std::make_pair(spaceNeeded.second, freeDiskSpaceRight.ToDouble()))); } //windows: check if recycle bin really exists; if not, Windows will silently delete, which is wrong #ifdef FFS_WIN if (folderPairCfg.handleDeletion == MOVE_TO_RECYCLE_BIN) { - if (statisticsFolderPair.getOverwrite() + - statisticsFolderPair.getDelete () > 0 && + if (folderPairStat.getOverwrite() + + folderPairStat.getDelete () > 0 && - recycleBinStatus(j->getBaseDir()) != STATUS_REC_EXISTS) - recyclMissing.insert(j->getBaseDir()); + recycleBinStatus(j->getBaseDirPf()) != STATUS_REC_EXISTS) + recyclMissing.insert(j->getBaseDirPf()); - if (statisticsFolderPair.getOverwrite() + - statisticsFolderPair.getDelete () > 0 && + if (folderPairStat.getOverwrite() + + folderPairStat.getDelete () > 0 && - recycleBinStatus(j->getBaseDir()) != STATUS_REC_EXISTS) - recyclMissing.insert(j->getBaseDir()); + recycleBinStatus(j->getBaseDirPf()) != STATUS_REC_EXISTS) + recyclMissing.insert(j->getBaseDirPf()); } #endif } @@ -1517,7 +1613,7 @@ void SyncProcess::startSynchronizationProcess(const std::vectorsecond; //conflictDescription.Replace(wxT("\n"), wxT(" ")); //remove line-breaks - warningMessage += wxString(wxT("\"")) + i->first + "\": " + conflictDescription + "\n\n"; + warningMessage += wxString(L"\"") + i->first + L"\": " + conflictDescription + "\n\n"; } if (statisticsTotal.getConflict() > static_cast(firstConflicts.size())) @@ -1538,7 +1634,7 @@ void SyncProcess::startSynchronizationProcess(const std::vectorfirst + " <-> " + "\n" + i->second; - warningMessage += wxString(wxT("\n\n")) +_("More than 50% of the total number of files will be copied or deleted!"); + warningMessage += wxString(wxT("\n\n")) + _("More than 50% of the total number of files will be copied or deleted!"); procCallback.reportWarning(warningMessage, m_warnings.warningSignificantDifference); } @@ -1603,9 +1699,9 @@ void SyncProcess::startSynchronizationProcess(const std::vector& delHandlerFp = delHandler[folderIndex]; @@ -1614,7 +1710,7 @@ void SyncProcess::startSynchronizationProcess(const std::vectorgetBaseDir(), j->getBaseDir())) + if (EqualFilename()(j->getBaseDirPf(), j->getBaseDirPf())) continue; //------------------------------------------------------------------------------------------ @@ -1624,20 +1720,20 @@ void SyncProcess::startSynchronizationProcess(const std::vectorgetBaseDir() + "\"" + " \n" + - "\t" + right + "\"" + j->getBaseDir() + "\""; + "\t" + left + "\"" + j->getBaseDirPf() + "\"" + " \n" + + "\t" + right + "\"" + j->getBaseDirPf() + "\""; procCallback.reportInfo(statusTxt); //------------------------------------------------------------------------------------------ //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 = j->getBaseDir().BeforeLast(FILE_NAME_SEPARATOR); + const Zstring dirnameLeft = j->getBaseDirPf().BeforeLast(FILE_NAME_SEPARATOR); if (!dirnameLeft.empty() && !dirExistsUpdating(dirnameLeft, procCallback)) { if (!tryReportingError(procCallback, [&]() { createDirectory(dirnameLeft); })) //may throw in error-callback! continue; //skip this folder pair } - const Zstring dirnameRight = j->getBaseDir().BeforeLast(FILE_NAME_SEPARATOR); + const Zstring dirnameRight = j->getBaseDirPf().BeforeLast(FILE_NAME_SEPARATOR); if (!dirnameRight.empty() && !dirExistsUpdating(dirnameRight, procCallback)) { if (!tryReportingError(procCallback, [&]() { createDirectory(dirnameRight); })) //may throw in error-callback! @@ -1652,8 +1748,7 @@ void SyncProcess::startSynchronizationProcess(const std::vectortryWriteDB(); }); } } + + if (!synchronizationNeeded(statisticsTotal)) + procCallback.reportInfo(_("Nothing to synchronize according to configuration!")); //inform about this special case } catch (const std::exception& e) { @@ -1716,127 +1814,78 @@ private: //copy file while refreshing UI -template -void SynchronizeFolderPair::copyFileUpdating(const Zstring& source, const Zstring& target, const DelTargetCommand& cmd, UInt64 totalBytesToCpy, int recursionLvl) const +template +void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const DelTargetCommand& cmd, FileDescriptor& sourceAttr) const { - const int exceptionPaths = 2; - - //start of (possibly) long-running copy process: ensure status updates are performed regularly - UInt64 bytesReported; - - //in error situation: undo communication of processed amount of data - Loki::ScopeGuard guardStatistics = Loki::MakeGuard([&]() { procCallback_.updateProcessedData(0, -1 * to(bytesReported)); }); + const UInt64 totalBytesToCpy = fileObj.getFileSize::result>(); + Zstring source = fileObj.getFullName::result>(); + const Zstring& target = fileObj.getBaseDirPf() + fileObj.getRelativeName::result>(); - try + auto copyOperation = [&]() { + //start of (possibly) long-running copy process: ensure status updates are performed regularly + UInt64 bytesReported; + //in error situation: undo communication of processed amount of data + Loki::ScopeGuard guardStatistics = Loki::MakeGuard([&]() { procCallback_.updateProcessedData(0, -1 * to(bytesReported)); }); + WhileCopying callback(bytesReported, procCallback_, cmd); + FileAttrib fileAttr; + + zen::copyFile(source, //type File implicitly means symlinks need to be dereferenced! + target, + copyFilePermissions, + transactionalFileCopy, + &callback, + &fileAttr); //throw FileError, ErrorFileLocked - copyFile(source, //type File implicitly means symlinks need to be dereferenced! - target, - copyFilePermissions, - transactionalFileCopy, - &callback); //throw (FileError, ErrorFileLocked); + sourceAttr = FileDescriptor(fileAttr.modificationTime, fileAttr.fileSize); //inform about the (remaining) processed amount of data procCallback_.updateProcessedData(0, to(totalBytesToCpy) - to(bytesReported)); bytesReported = totalBytesToCpy; - } + + guardStatistics.Dismiss(); + }; + #ifdef FFS_WIN + try + { + copyOperation(); + } catch (ErrorFileLocked&) { - if (recursionLvl >= exceptionPaths) throw; - //if file is locked (try to) use Windows Volume Shadow Copy Service - if (shadowCopyHandler_ == NULL) throw; - - Zstring shadowFilename; + if (shadowCopyHandler_ == NULL) + throw; try { //contains prefix: E.g. "\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Program Files\FFS\sample.dat" - shadowFilename = shadowCopyHandler_->makeShadowCopy(source); //throw (FileError) + source = shadowCopyHandler_->makeShadowCopy(source); //throw FileError } catch (const FileError& e) { - std::wstring errorMsg = _("Error copying locked file %x!"); - replace(errorMsg, L"%x", std::wstring(L"\"") + source + "\""); + const std::wstring errorMsg = replaceCpy(_("Error copying locked file %x!"), L"%x", std::wstring(L"\"") + source + "\""); throw FileError(errorMsg + "\n\n" + e.msg()); } //now try again - return copyFileUpdating(shadowFilename, target, cmd, totalBytesToCpy, recursionLvl + 1); + copyOperation(); } +#else + copyOperation(); #endif - catch (ErrorTargetPathMissing&) - { - if (recursionLvl >= exceptionPaths) throw; - //create folders "first" (see http://sourceforge.net/tracker/index.php?func=detail&aid=2628943&group_id=234430&atid=1093080) - //using optimistic strategy: assume everything goes well, but cover up on error -> minimize file accesses - const Zstring targetDir = target.BeforeLast(FILE_NAME_SEPARATOR); - const Zstring templateDir = source.BeforeLast(FILE_NAME_SEPARATOR); - if (!targetDir.empty()) - { - createDirectory(targetDir, templateDir, copyFilePermissions); //throw (FileError) - /*symbolic link handling: - if "not traversing symlinks": fullName == c:\syncdir\some\dirs\leaf - => setting irrelevant - if "traversing symlinks": fullName == c:\syncdir\some\dirs\leaf - => setting NEEDS to be false: We want to move leaf, therefore symlinks in "some\dirs" must not interfere */ - - //now try again - return copyFileUpdating(source, target, cmd, totalBytesToCpy, recursionLvl + 1); - } - throw; - } - - //todo: transactional behavior: delete target if verification fails? - - if (verifyCopiedFiles) //verify if data was copied correctly - verifyFileCopy(source, target); //throw (FileError) - - guardStatistics.Dismiss(); -} - - -void SynchronizeFolderPair::copySymlink(const Zstring& source, const Zstring& target, LinkDescriptor::LinkType type, bool inRecursion) const -{ - try + //#################### Verification ############################# + if (verifyCopiedFiles) { - switch (type) - { - case LinkDescriptor::TYPE_DIR: - zen::copySymlink(source, target, SYMLINK_TYPE_DIR, copyFilePermissions); //throw (FileError) - break; + Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, target); //delete target if verification fails + Loki::ScopeGuard guardStatistics = Loki::MakeGuard([&]() { procCallback_.updateProcessedData(0, -1 * to(totalBytesToCpy)); }); - case LinkDescriptor::TYPE_FILE: //Windows: true file symlink; Linux: file-link or broken link - zen::copySymlink(source, target, SYMLINK_TYPE_FILE, copyFilePermissions); //throw (FileError) - break; - } - } - catch (FileError&) - { - if (inRecursion) throw; - - //create folders "first" (see http://sourceforge.net/tracker/index.php?func=detail&aid=2628943&group_id=234430&atid=1093080) - //using optimistic strategy: assume everything goes well, but cover up on error -> minimize file accesses - const Zstring targetDir = target.BeforeLast(FILE_NAME_SEPARATOR); - const Zstring templateDir = source.BeforeLast(FILE_NAME_SEPARATOR); - - if (!targetDir.empty() && !dirExistsUpdating(targetDir, procCallback_)) - { - createDirectory(targetDir, templateDir, copyFilePermissions); //throw (FileError) - /*symbolic link handling: - if "not traversing symlinks": fullName == c:\syncdir\some\dirs\leaf - => setting irrelevant - if "traversing symlinks": fullName == c:\syncdir\some\dirs\leaf - => setting NEEDS to be false: We want to move leaf, therefore symlinks in "some\dirs" must not interfere */ - - //now try again - return copySymlink(source, target, type, true); - } + verifyFileCopy(source, target); //throw FileError - throw; + guardTarget.Dismiss(); + guardStatistics.Dismiss(); } } @@ -1849,11 +1898,11 @@ void SynchronizeFolderPair::deleteSymlink(const SymLinkMapping& linkObj) const switch (linkObj.getLinkType()) { case LinkDescriptor::TYPE_DIR: - delHandling.removeFolder(linkObj.getObjRelativeName()); //throw (FileError) + delHandling.removeFolder(linkObj.getObjRelativeName()); //throw FileError break; case LinkDescriptor::TYPE_FILE: //Windows: true file symlink; Linux: file-link or broken link - delHandling.removeFile(linkObj.getObjRelativeName()); //throw (FileError) + delHandling.removeFile(linkObj.getObjRelativeName()); //throw FileError break; } } @@ -1926,9 +1975,8 @@ private: void SynchronizeFolderPair::verifyFileCopy(const Zstring& source, const Zstring& target) const { - Zstring statusText = txtVerifying; - statusText.Replace(Zstr("%x"), target, false); - procCallback_.reportInfo(utf8CvrtTo(statusText)); + wxString logText = replaceCpy(txtVerifying, L"%x", utf8CvrtTo(target)); + procCallback_.reportInfo(logText); VerifyStatusUpdater callback(procCallback_); -- cgit