diff options
Diffstat (limited to 'synchronization.cpp')
-rw-r--r-- | synchronization.cpp | 1423 |
1 files changed, 864 insertions, 559 deletions
diff --git a/synchronization.cpp b/synchronization.cpp index 66c7d262..8a5027be 100644 --- a/synchronization.cpp +++ b/synchronization.cpp @@ -8,7 +8,7 @@ #include <memory> #include <deque> #include <stdexcept> -#include <wx/msgdlg.h> +#include <wx/utils.h> #include <wx/log.h> #include <wx/file.h> #include <wx+/string_conv.h> @@ -32,36 +32,55 @@ using namespace zen; +namespace +{ +inline +int getCUD(const SyncStatistics& stat) +{ + return stat.getCreate() + + stat.getUpdate() + + stat.getDelete(); +} +} + void SyncStatistics::init() { - createLeft = 0; - createRight = 0; - overwriteLeft = 0; - overwriteRight = 0; - deleteLeft = 0; - deleteRight = 0; - conflict = 0; - rowsTotal = 0; + createLeft = 0; + createRight = 0; + updateLeft = 0; + updateRight = 0; + deleteLeft = 0; + deleteRight = 0; + conflict = 0; + rowsTotal = 0; } SyncStatistics::SyncStatistics(const FolderComparison& folderCmp) { init(); - std::for_each(begin(folderCmp), end(folderCmp), [&](const BaseDirMapping& baseMap) { getNumbersRecursively(baseMap); }); + std::for_each(begin(folderCmp), end(folderCmp), [&](const BaseDirMapping& baseMap) { recurse(baseMap); }); } SyncStatistics::SyncStatistics(const HierarchyObject& hierObj) { init(); - getNumbersRecursively(hierObj); + recurse(hierObj); +} + + +SyncStatistics::SyncStatistics(const FileMapping& fileObj) +{ + init(); + getFileNumbers(fileObj); + rowsTotal += 1; } inline -void SyncStatistics::getNumbersRecursively(const HierarchyObject& hierObj) +void SyncStatistics::recurse(const HierarchyObject& hierObj) { std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(), [&](const DirMapping& dirObj) { getDirNumbers(dirObj); }); @@ -101,13 +120,25 @@ void SyncStatistics::getFileNumbers(const FileMapping& fileObj) ++deleteRight; break; + case SO_MOVE_LEFT_TARGET: + ++updateLeft; + break; + + case SO_MOVE_RIGHT_TARGET: + ++updateRight; + break; + + case SO_MOVE_LEFT_SOURCE: //ignore + case SO_MOVE_RIGHT_SOURCE: // + break; + case SO_OVERWRITE_LEFT: - ++overwriteLeft; + ++updateLeft; dataToProcess += fileObj.getFileSize<RIGHT_SIDE>(); break; case SO_OVERWRITE_RIGHT: - ++overwriteRight; + ++updateRight; dataToProcess += fileObj.getFileSize<LEFT_SIDE>(); break; @@ -118,11 +149,11 @@ void SyncStatistics::getFileNumbers(const FileMapping& fileObj) break; case SO_COPY_METADATA_TO_LEFT: - ++overwriteLeft; + ++updateLeft; break; case SO_COPY_METADATA_TO_RIGHT: - ++overwriteRight; + ++updateRight; break; case SO_DO_NOTHING: @@ -155,12 +186,12 @@ void SyncStatistics::getLinkNumbers(const SymLinkMapping& linkObj) case SO_OVERWRITE_LEFT: case SO_COPY_METADATA_TO_LEFT: - ++overwriteLeft; + ++updateLeft; break; case SO_OVERWRITE_RIGHT: case SO_COPY_METADATA_TO_RIGHT: - ++overwriteRight; + ++updateRight; break; case SO_UNRESOLVED_CONFLICT: @@ -169,6 +200,11 @@ void SyncStatistics::getLinkNumbers(const SymLinkMapping& linkObj) firstConflicts.push_back(std::make_pair(linkObj.getObjRelativeName(), linkObj.getSyncOpConflict())); break; + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_TARGET: + assert(false); case SO_DO_NOTHING: case SO_EQUAL: break; @@ -209,20 +245,24 @@ void SyncStatistics::getDirNumbers(const DirMapping& dirObj) break; case SO_COPY_METADATA_TO_LEFT: - ++overwriteLeft; + ++updateLeft; break; case SO_COPY_METADATA_TO_RIGHT: - ++overwriteRight; + ++updateRight; break; + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_TARGET: + assert(false); case SO_DO_NOTHING: case SO_EQUAL: break; } - //recurse into sub-dirs - getNumbersRecursively(dirObj); + recurse(dirObj); } @@ -245,7 +285,7 @@ std::vector<zen::FolderPairSyncCfg> zen::extractSyncCfg(const MainConfiguration& output.push_back( FolderPairSyncCfg(syncCfg.directionCfg.var == DirectionConfig::AUTOMATIC, syncCfg.handleDeletion, - toZ(syncCfg.customDeletionDirectory))); + syncCfg.customDeletionDirectory)); } return output; @@ -257,7 +297,7 @@ namespace bool synchronizationNeeded(const SyncStatistics& statisticsTotal) { return statisticsTotal.getCreate() + - statisticsTotal.getOverwrite() + + statisticsTotal.getUpdate() + statisticsTotal.getDelete() + //conflicts (unless excluded) justify a sync! Also note: if this method returns false, no warning about unresolved conflicts were shown! statisticsTotal.getConflict() != 0; @@ -269,18 +309,18 @@ bool synchronizationNeeded(const SyncStatistics& statisticsTotal) bool significantDifferenceDetected(const SyncStatistics& folderPairStat) { //initial file copying shall not be detected as major difference - if (folderPairStat.getCreate<LEFT_SIDE>() == 0 && - folderPairStat.getOverwrite() == 0 && - folderPairStat.getDelete() == 0 && - folderPairStat.getConflict() == 0) return false; + if (folderPairStat.getCreate<LEFT_SIDE>() == 0 && + folderPairStat.getUpdate () == 0 && + folderPairStat.getDelete () == 0 && + folderPairStat.getConflict() == 0) return false; if (folderPairStat.getCreate<RIGHT_SIDE>() == 0 && - folderPairStat.getOverwrite() == 0 && - folderPairStat.getDelete() == 0 && - folderPairStat.getConflict() == 0) return false; + folderPairStat.getUpdate () == 0 && + folderPairStat.getDelete () == 0 && + folderPairStat.getConflict() == 0) return false; - const int changedRows = folderPairStat.getCreate() + - folderPairStat.getOverwrite() + - folderPairStat.getDelete() + + const int changedRows = folderPairStat.getCreate () + + folderPairStat.getUpdate () + + folderPairStat.getDelete () + folderPairStat.getConflict(); return changedRows >= 10 && changedRows > 0.5 * folderPairStat.getRowCount(); @@ -361,10 +401,10 @@ SyncProcess::SyncProcess(xmlAccess::OptionalDialogs& warnings, transactionalFileCopy_(transactionalFileCopy), m_warnings(warnings), procCallback(handler) - { -if (runWithBackgroundPriority) -procBackground.reset(new ScheduleForBackgroundProcessing); - } +{ + if (runWithBackgroundPriority) + procBackground.reset(new ScheduleForBackgroundProcessing); +} //-------------------------------------------------------------------------------------------------------------- namespace @@ -377,7 +417,7 @@ class DeletionHandling //e.g. generate name of alternate deletion directory (uni public: DeletionHandling(DeletionPolicy handleDel, const Zstring& custDelFolder, - const Zstring& baseDir, //with separator postfix + const Zstring& baseDirPf, //with separator postfix ProcessCallback& procCallback); ~DeletionHandling(); //always (try to) clean up, even if synchronization is aborted! @@ -387,9 +427,9 @@ public: void removeFile (const Zstring& relativeName) const; //throw FileError void removeFolder(const Zstring& relativeName) const; //throw FileError - const wxString& getTxtRemovingFile () const { return txtRemovingFile; } // - const wxString& getTxtRemovingSymLink() const { return txtRemovingSymlink; } //status text templates - const wxString& getTxtRemovingDir () const { return txtRemovingDirectory; } // + const std::wstring& getTxtRemovingFile () const { return txtRemovingFile; } // + const std::wstring& getTxtRemovingSymLink() const { return txtRemovingSymlink; } //status text templates + const std::wstring& getTxtRemovingDir () const { return txtRemovingDirectory; } // //evaluate whether a deletion will actually free space within a volume bool deletionFreesSpace() const; @@ -401,12 +441,12 @@ private: Zstring sessionDelDir; //target deletion folder for current folder pair (with timestamp, ends with path separator) - Zstring baseDir_; //with separator postfix + Zstring baseDirPf_; //with separator postfix //preloaded status texts: - wxString txtRemovingFile; - wxString txtRemovingSymlink; - wxString txtRemovingDirectory; + std::wstring txtRemovingFile; + std::wstring txtRemovingSymlink; + std::wstring txtRemovingDirectory; bool cleanedUp; }; @@ -414,41 +454,41 @@ private: DeletionHandling::DeletionHandling(DeletionPolicy handleDel, const Zstring& custDelFolder, - const Zstring& baseDir, //with separator postfix + const Zstring& baseDirPf, //with separator postfix ProcessCallback& procCallback) : deletionType(handleDel), procCallback_(&procCallback), - baseDir_(baseDir), + baseDirPf_(baseDirPf), cleanedUp(false) { #ifdef FFS_WIN - if (baseDir.empty() || - (deletionType == MOVE_TO_RECYCLE_BIN && recycleBinStatus(baseDir) != STATUS_REC_EXISTS)) + if (baseDirPf.empty() || + (deletionType == MOVE_TO_RECYCLE_BIN && recycleBinStatus(baseDirPf) != 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 = 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); + txtRemovingFile = replaceCpy(_("Deleting file %x" ), L"%x", L"\"%x\"", false); + txtRemovingDirectory = replaceCpy(_("Deleting folder %x" ), L"%x", L"\"%x\"", false); + txtRemovingSymlink = replaceCpy(_("Deleting symbolic link %x"), L"%x", L"\"%x\"", false); break; case MOVE_TO_RECYCLE_BIN: - sessionDelDir = getSessionDeletionDir(baseDir_, Zstr("FFS ")); + sessionDelDir = getSessionDeletionDir(baseDirPf_, Zstr("FFS ")); - 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); + txtRemovingFile = replaceCpy(_("Moving file %x to recycle bin" ), L"%x", L"\"%x\"", false); + txtRemovingDirectory = replaceCpy(_("Moving folder %x to recycle bin" ), L"%x", L"\"%x\"", false); + txtRemovingSymlink = replaceCpy(_("Moving symbolic link %x to recycle bin"), L"%x", L"\"%x\"", false); break; case MOVE_TO_CUSTOM_DIRECTORY: sessionDelDir = getSessionDeletionDir(custDelFolder); - txtRemovingFile = replaceCpy(replaceCpy(_("Moving file %x to %y" ), L"%x", L"\n\"%x\"", false), L"%y", L"\"" + utf8CvrtTo<wxString>(custDelFolder) + L"\"", false); - txtRemovingDirectory = replaceCpy(replaceCpy(_("Moving folder %x to %y" ), L"%x", L"\n\"%x\"", false), L"%y", L"\"" + utf8CvrtTo<wxString>(custDelFolder) + L"\"", false); - txtRemovingSymlink = replaceCpy(replaceCpy(_("Moving symbolic link %x to %y"), L"%x", L"\n\"%x\"", false), L"%y", L"\"" + utf8CvrtTo<wxString>(custDelFolder) + L"\"", false); + txtRemovingFile = replaceCpy(replaceCpy(_("Moving file %x to %y" ), L"%x", L"\"%x\"", false), L"%y", L"\"" + utf8CvrtTo<std::wstring>(custDelFolder) + L"\"", false); + txtRemovingDirectory = replaceCpy(replaceCpy(_("Moving folder %x to %y" ), L"%x", L"\"%x\"", false), L"%y", L"\"" + utf8CvrtTo<std::wstring>(custDelFolder) + L"\"", false); + txtRemovingSymlink = replaceCpy(replaceCpy(_("Moving symbolic link %x to %y"), L"%x", L"\"%x\"", false), L"%y", L"\"" + utf8CvrtTo<std::wstring>(custDelFolder) + L"\"", false); break; } } @@ -497,12 +537,12 @@ struct CallbackRemoveDirImpl : public CallbackRemoveDir virtual void notifyFileDeletion(const Zstring& filename) { - delHandling_.procCallback_->reportStatus(replaceCpy(delHandling_.getTxtRemovingFile(), L"%x", utf8CvrtTo<wxString>(filename))); + delHandling_.procCallback_->reportStatus(replaceCpy(delHandling_.getTxtRemovingFile(), L"%x", utf8CvrtTo<std::wstring>(filename))); } virtual void notifyDirDeletion(const Zstring& dirname) { - delHandling_.procCallback_->reportStatus(replaceCpy(delHandling_.getTxtRemovingDir(), L"%x", utf8CvrtTo<wxString>(dirname))); + delHandling_.procCallback_->reportStatus(replaceCpy(delHandling_.getTxtRemovingDir(), L"%x", utf8CvrtTo<std::wstring>(dirname))); } private: @@ -513,7 +553,7 @@ private: void DeletionHandling::removeFile(const Zstring& relativeName) const { - const Zstring fullName = baseDir_ + relativeName; + const Zstring fullName = baseDirPf_ + relativeName; switch (deletionType) { @@ -562,7 +602,7 @@ void DeletionHandling::removeFile(const Zstring& relativeName) const void DeletionHandling::removeFolder(const Zstring& relativeName) const { - const Zstring fullName = baseDir_ + relativeName; + const Zstring fullName = baseDirPf_ + relativeName; switch (deletionType) { @@ -621,7 +661,7 @@ bool DeletionHandling::deletionFreesSpace() const case MOVE_TO_RECYCLE_BIN: return false; //in general... (unless Recycle Bin is full) case MOVE_TO_CUSTOM_DIRECTORY: - switch (zen::onSameVolume(baseDir_, sessionDelDir)) + switch (zen::onSameVolume(baseDirPf_, sessionDelDir)) { case IS_SAME_YES: return false; @@ -642,20 +682,21 @@ namespace class DiskSpaceNeeded { public: - DiskSpaceNeeded(const BaseDirMapping& baseObj, bool freeSpaceDelLeft, bool freeSpaceDelRight) : - freeSpaceDelLeft_(freeSpaceDelLeft), - freeSpaceDelRight_(freeSpaceDelRight) + static std::pair<Int64, Int64> calculate(const BaseDirMapping& baseObj, bool freeSpaceDelLeft, bool freeSpaceDelRight) { - processRecursively(baseObj); + DiskSpaceNeeded inst(baseObj, freeSpaceDelLeft, freeSpaceDelRight); + return std::make_pair(inst.spaceNeededLeft, inst.spaceNeededRight); } - std::pair<zen::Int64, zen::Int64> getSpaceTotal() const +private: + DiskSpaceNeeded(const BaseDirMapping& baseObj, bool freeSpaceDelLeft, bool freeSpaceDelRight) : + freeSpaceDelLeft_(freeSpaceDelLeft), + freeSpaceDelRight_(freeSpaceDelRight) { - return std::make_pair(spaceNeededLeft, spaceNeededRight); + recurse(baseObj); } -private: - void processRecursively(const HierarchyObject& hierObj) + void recurse(const HierarchyObject& hierObj) { //don't process directories @@ -664,33 +705,33 @@ private: switch (i->getSyncOperation()) //evaluate comparison result and sync direction { case SO_CREATE_NEW_LEFT: - spaceNeededLeft += to<zen::Int64>(i->getFileSize<RIGHT_SIDE>()); + spaceNeededLeft += to<Int64>(i->getFileSize<RIGHT_SIDE>()); break; case SO_CREATE_NEW_RIGHT: - spaceNeededRight += to<zen::Int64>(i->getFileSize<LEFT_SIDE>()); + spaceNeededRight += to<Int64>(i->getFileSize<LEFT_SIDE>()); break; case SO_DELETE_LEFT: if (freeSpaceDelLeft_) - spaceNeededLeft -= to<zen::Int64>(i->getFileSize<LEFT_SIDE>()); + spaceNeededLeft -= to<Int64>(i->getFileSize<LEFT_SIDE>()); break; case SO_DELETE_RIGHT: if (freeSpaceDelRight_) - spaceNeededRight -= to<zen::Int64>(i->getFileSize<RIGHT_SIDE>()); + spaceNeededRight -= to<Int64>(i->getFileSize<RIGHT_SIDE>()); break; case SO_OVERWRITE_LEFT: if (freeSpaceDelLeft_) - spaceNeededLeft -= to<zen::Int64>(i->getFileSize<LEFT_SIDE>()); - spaceNeededLeft += to<zen::Int64>(i->getFileSize<RIGHT_SIDE>()); + spaceNeededLeft -= to<Int64>(i->getFileSize<LEFT_SIDE>()); + spaceNeededLeft += to<Int64>(i->getFileSize<RIGHT_SIDE>()); break; case SO_OVERWRITE_RIGHT: if (freeSpaceDelRight_) - spaceNeededRight -= to<zen::Int64>(i->getFileSize<RIGHT_SIDE>()); - spaceNeededRight += to<zen::Int64>(i->getFileSize<LEFT_SIDE>()); + spaceNeededRight -= to<Int64>(i->getFileSize<RIGHT_SIDE>()); + spaceNeededRight += to<Int64>(i->getFileSize<LEFT_SIDE>()); break; case SO_DO_NOTHING: @@ -698,6 +739,10 @@ private: case SO_UNRESOLVED_CONFLICT: case SO_COPY_METADATA_TO_LEFT: case SO_COPY_METADATA_TO_RIGHT: + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_TARGET: break; } @@ -705,78 +750,20 @@ private: //[...] //recurse into sub-dirs - std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(), [&](const HierarchyObject& subDir) { this->processRecursively(subDir); }); + std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(), [&](const HierarchyObject& subDir) { this->recurse(subDir); }); //for (auto& subDir : hierObj.refSubDirs()) - // processRecursively(subDir); + // recurse(subDir); } const bool freeSpaceDelLeft_; const bool freeSpaceDelRight_; - zen::Int64 spaceNeededLeft; - zen::Int64 spaceNeededRight; + Int64 spaceNeededLeft; + Int64 spaceNeededRight; }; } //---------------------------------------------------------------------------------------- -//test if current sync-line will result in deletion of files or -//big file is overwritten by smaller one -> used to avoid disc space bottlenecks (at least if permanent deletion is active) -inline -bool diskSpaceIsReduced(const FileMapping& fileObj) -{ - switch (fileObj.getSyncOperation()) //evaluate comparison result and sync direction - { - case SO_DELETE_LEFT: - case SO_DELETE_RIGHT: - return true; - - case SO_OVERWRITE_LEFT: - return fileObj.getFileSize<LEFT_SIDE>() > fileObj.getFileSize<RIGHT_SIDE>(); - - case SO_OVERWRITE_RIGHT: - return fileObj.getFileSize<LEFT_SIDE>() < fileObj.getFileSize<RIGHT_SIDE>(); - - case SO_CREATE_NEW_LEFT: - case SO_CREATE_NEW_RIGHT: - case SO_DO_NOTHING: - case SO_EQUAL: - case SO_UNRESOLVED_CONFLICT: - case SO_COPY_METADATA_TO_LEFT: - case SO_COPY_METADATA_TO_RIGHT: - return false; - } - assert(false); - return false; //dummy -} - - -inline -bool diskSpaceIsReduced(const DirMapping& dirObj) -{ - switch (dirObj.getSyncOperation()) //evaluate comparison result and sync direction - { - case SO_DELETE_LEFT: - case SO_DELETE_RIGHT: - return true; - - case SO_OVERWRITE_LEFT: - case SO_OVERWRITE_RIGHT: - assert(false); - case SO_CREATE_NEW_LEFT: - case SO_CREATE_NEW_RIGHT: - case SO_DO_NOTHING: - case SO_EQUAL: - case SO_UNRESOLVED_CONFLICT: - case SO_COPY_METADATA_TO_LEFT: - case SO_COPY_METADATA_TO_RIGHT: - return false; - } - assert(false); - return false; //dummy -} -//--------------------------------------------------------------------------------------------------------------- - - class zen::SynchronizeFolderPair { public: @@ -801,36 +788,58 @@ public: txtOverwritingFile (replaceCpy(_("Overwriting file %x" ), L"%x", L"\"%x\"", false)), txtOverwritingLink (replaceCpy(_("Overwriting symbolic link %x"), L"%x", L"\"%x\"", false)), txtVerifying (replaceCpy(_("Verifying file %x" ), L"%x", L"\"%x\"", false)), - txtWritingAttributes(replaceCpy(_("Updating attributes of %x" ), L"%x", L"\"%x\"", false)) {} + txtWritingAttributes(replaceCpy(_("Updating attributes of %x" ), L"%x", L"\"%x\"", false)), + txtMovingFile (replaceCpy(replaceCpy(_("Moving file %x to %y"), L"%x", L"\"%x\"", false), L"%y", L"\"%y\"", false)) {} void startSync(BaseDirMapping& baseMap) { - //loop through all files twice; reason: first delete files (or overwrite big ones with smaller ones), then copy rest - execute<FIRST_PASS >(baseMap); - execute<SECOND_PASS>(baseMap); + runZeroPass(baseMap); //first process file moves + runPass<PASS_ONE>(baseMap); //delete files (or overwrite big ones with smaller ones) + runPass<PASS_TWO>(baseMap); //copy rest } private: enum PassId { - FIRST_PASS, //delete files - SECOND_PASS //create, modify + PASS_ONE, //delete files + PASS_TWO, //create, modify + PASS_NEVER //skip }; + static PassId getPass(const FileMapping& fileObj); + static PassId getPass(const SymLinkMapping& linkObj); + static PassId getPass(const DirMapping& dirObj); + + template <SelectedSide side> + void prepare2StepMove(FileMapping& sourceObj, FileMapping& targetObj); //throw FileError + bool createParentDir(FileSystemObject& fsObj); //throw FileError + template <SelectedSide side> + void manageFileMove(FileMapping& sourceObj, FileMapping& targetObj); //throw FileError + + void runZeroPass(HierarchyObject& hierObj); template <PassId pass> - void execute(HierarchyObject& hierObj); + void runPass(HierarchyObject& hierObj); + + void synchronizeFile(FileMapping& fileObj) const; + template <SelectedSide side> void synchronizeFileInt(FileMapping& fileObj, SyncOperation syncOp) const; + + void synchronizeLink(SymLinkMapping& linkObj) const; + template <SelectedSide sideTrg> void synchronizeLinkInt(SymLinkMapping& linkObj, SyncOperation syncOp) const; + + void synchronizeFolder(DirMapping& dirObj) const; + template <SelectedSide sideTrg> void synchronizeFolderInt(DirMapping& dirObj, SyncOperation syncOp) const; - void synchronizeFile (FileMapping& fileObj) const; - void synchronizeLink (SymLinkMapping& linkObj) const; - void synchronizeFolder(DirMapping& dirObj) const; + void reportInfo (const std::wstring& rawText, const Zstring& objname) const { procCallback_.reportInfo (replaceCpy(rawText, L"%x", utf8CvrtTo<std::wstring>(objname))); }; + void reportStatus(const std::wstring& rawText, const Zstring& objname) const { procCallback_.reportStatus(replaceCpy(rawText, L"%x", utf8CvrtTo<std::wstring>(objname))); }; //more low level helper - template <zen::SelectedSide side> - void deleteSymlink(const SymLinkMapping& linkObj) const; template <SelectedSide side, class DelTargetCommand> - void copyFileUpdatingTo(const FileMapping& fileObj, const DelTargetCommand& cmd, FileDescriptor& sourceAttr) const; + void copyFileUpdatingTo(const FileMapping& fileObj, const DelTargetCommand& cmd, FileAttrib& newAttr) const; void verifyFileCopy(const Zstring& source, const Zstring& target) const; + template <SelectedSide side> + const DeletionHandling& getDelHandling() const; + ProcessCallback& procCallback_; #ifdef FFS_WIN shadow::ShadowCopy* shadowCopyHandler_; //optional! @@ -843,198 +852,556 @@ private: const bool transactionalFileCopy; //preload status texts - const wxString txtCreatingFile; - const wxString txtCreatingLink; - const wxString txtCreatingFolder; - const wxString txtOverwritingFile; - const wxString txtOverwritingLink; - const wxString txtVerifying; - const wxString txtWritingAttributes; + const std::wstring txtCreatingFile; + const std::wstring txtCreatingLink; + const std::wstring txtCreatingFolder; + const std::wstring txtOverwritingFile; + const std::wstring txtOverwritingLink; + const std::wstring txtVerifying; + const std::wstring txtWritingAttributes; + const std::wstring txtMovingFile; }; +//--------------------------------------------------------------------------------------------------------------- +namespace zen +{ +template <> inline +const DeletionHandling& SynchronizeFolderPair::getDelHandling<LEFT_SIDE>() const { return delHandlingLeft_; } + +template <> inline +const DeletionHandling& SynchronizeFolderPair::getDelHandling<RIGHT_SIDE>() const { return delHandlingRight_; } +} + + +/* +__________________________ +|Move algorithm, 0th pass| +-------------------------- +1. loop over hierarchy and find "move source" + +2. check whether parent directory of "move source" is going to be deleted or location of "move source" may lead to name clash with other dir/symlink + -> no: delay move until 2nd pass + +3. create move target's parent directory recursively + execute move + do we have name clash? + -> prepare a 2-step move operation: 1. move source to root and update "move target" accordingly 2. delay move until 2nd pass + +4. If any of the operations above did not succeed (even after retry), update statistics and revert to "copy + delete" + Note: first pass may delete "move source"!!! + +__________________ +|killer-scenarios| +------------------ +propagate the following move sequences: +I) a -> a/a caveat sync'ing parent directory first leads to circular dependency! + +II) a/a -> a caveat: fixing name clash will remove source! + +III) c -> d caveat: move-sequence needs to be processed in correct order! + b -> c/b + a -> b/a +*/ + +namespace +{ +template <class Mapping> inline +bool haveNameClash(const Zstring& shortname, Mapping& m) +{ + return std::find_if(m.begin(), m.end(), + [&](const typename Mapping::value_type& obj) { return EqualFilename()(obj.getObjShortName(), shortname); }) != m.end(); +} + + +Zstring findUniqueTempName(const Zstring& filename) +{ + Zstring output = filename + zen::TEMP_FILE_ENDING; + + //ensure uniqueness + for (int i = 1; somethingExists(output); ++i) + output = filename + Zchar('_') + toString<Zstring>(i) + zen::TEMP_FILE_ENDING; + + return output; +} +} + + +template <SelectedSide side> +void SynchronizeFolderPair::prepare2StepMove(FileMapping& sourceObj, + FileMapping& targetObj) //throw FileError +{ + const Zstring& source = sourceObj.getFullName<side>(); + const Zstring& tmpTarget = findUniqueTempName(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 + //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", utf8CvrtTo<std::wstring>(tmpTarget)), source); + + renameFile(source, tmpTarget); //throw FileError; + + //update file hierarchy + const FileDescriptor descrSource(sourceObj.getLastWriteTime<side>(), + sourceObj.getFileSize <side>(), + sourceObj.getFileId <side>()); + + sourceObj.removeObject<side>(); + + FileMapping& tempFile = sourceObj.root().addSubFile<side>(afterLast(tmpTarget, FILE_NAME_SEPARATOR), descrSource); + + //prepare move in second pass + tempFile.setSyncDir(side == LEFT_SIDE ? SYNC_DIR_LEFT : SYNC_DIR_RIGHT); + + targetObj.setMoveRef(tempFile .getId()); + tempFile .setMoveRef(targetObj.getId()); + + //NO statistics update! + procCallback_.requestUiRefresh(); //may throw +} + + +bool SynchronizeFolderPair::createParentDir(FileSystemObject& fsObj) //throw FileError, "false" on name clash +{ + if (DirMapping* parentDir = dynamic_cast<DirMapping*>(&fsObj.parent())) + { + if (!createParentDir(*parentDir)) + return false; + + //detect (and try to resolve) file type conflicts: 1. symlinks 2. files + const Zstring& shortname = parentDir->getObjShortName(); + if (haveNameClash(shortname, parentDir->parent().refSubLinks()) || + haveNameClash(shortname, parentDir->parent().refSubFiles())) + return false; + + //in this context "parentDir" cannot be scheduled for deletion since it contains a "move target"! + //note: if parentDir were deleted, we'd end up destroying "fsObj"! + assert(parentDir->getSyncOperation() != SO_DELETE_LEFT && + parentDir->getSyncOperation() != SO_DELETE_RIGHT); + + synchronizeFolder(*parentDir); //throw FileError + } + return true; +} + + +template <SelectedSide side> +void SynchronizeFolderPair::manageFileMove(FileMapping& sourceObj, + FileMapping& targetObj) //throw FileError +{ + assert((sourceObj.getSyncOperation() == SO_MOVE_LEFT_SOURCE && targetObj.getSyncOperation() == SO_MOVE_LEFT_TARGET && side == LEFT_SIDE) || + (sourceObj.getSyncOperation() == SO_MOVE_RIGHT_SOURCE && targetObj.getSyncOperation() == SO_MOVE_RIGHT_TARGET && side == RIGHT_SIDE)); + + const bool sourceWillBeDeleted = [&]() -> bool + { + if (DirMapping* parentDir = dynamic_cast<DirMapping*>(&sourceObj.parent())) + { + switch (parentDir->getSyncOperation()) //evaluate comparison result and sync direction + { + case SO_DELETE_LEFT: + case SO_DELETE_RIGHT: + return true; //we need to do something about it + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_TARGET: + case SO_OVERWRITE_LEFT: + case SO_OVERWRITE_RIGHT: + case SO_CREATE_NEW_LEFT: + case SO_CREATE_NEW_RIGHT: + case SO_DO_NOTHING: + case SO_EQUAL: + case SO_UNRESOLVED_CONFLICT: + case SO_COPY_METADATA_TO_LEFT: + case SO_COPY_METADATA_TO_RIGHT: + break; + } + } + return false; + }(); + + auto haveNameClash = [](const FileMapping& fileObj) + { + return ::haveNameClash(fileObj.getObjShortName(), fileObj.parent().refSubLinks()) || + ::haveNameClash(fileObj.getObjShortName(), fileObj.parent().refSubDirs()); + }; + + if (sourceWillBeDeleted || haveNameClash(sourceObj)) + { + //prepare for move now: - revert to 2-step move on name clashes + if (haveNameClash(targetObj) || + !createParentDir(targetObj)) //throw FileError + return prepare2StepMove<side>(sourceObj, targetObj); //throw FileError + + //finally start move! this should work now: + synchronizeFile(sourceObj); //throw FileError + } + //else: sourceObj will not be deleted, and is not standing in the way => delay to second pass + //note: this case may include "move sources" from two-step sub-routine! +} + + +void SynchronizeFolderPair::runZeroPass(HierarchyObject& hierObj) +{ + //search for file move-operations + + for (auto iter = hierObj.refSubFiles().begin(); iter != hierObj.refSubFiles().end(); ++iter) //VS 2010 crashes if we use for_each + lambda here... + { + FileMapping& fileObj = *iter; + + const SyncOperation syncOp = fileObj.getSyncOperation(); + switch (syncOp) //evaluate comparison result and sync direction + { + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_RIGHT_SOURCE: + { + FileMapping* sourceObj = &fileObj; + if (FileMapping* targetObj = dynamic_cast<FileMapping*>(FileSystemObject::retrieve(fileObj.getMoveRef()))) + { + if (!tryReportingError(procCallback_, [&] { return syncOp == SO_MOVE_LEFT_SOURCE ? this->manageFileMove<LEFT_SIDE>(*sourceObj, *targetObj) : this->manageFileMove<RIGHT_SIDE>(*sourceObj, *targetObj); })) + { + //move operation has failed! We cannot allow to continue and have move source's parent directory deleted, messing up statistics! + // => revert to ordinary "copy + delete" + + auto getStat = [&]() -> std::pair<int, Int64> + { + SyncStatistics statSrc(*sourceObj); + SyncStatistics statTrg(*targetObj); + + return std::make_pair(getCUD(statSrc) + getCUD(statTrg), + to<Int64>(statSrc.getDataToProcess() + statTrg.getDataToProcess())); + }; + + const auto statBefore = getStat(); + sourceObj->setMoveRef(NULL); + targetObj->setMoveRef(NULL); + const auto statAfter = getStat(); + //fix statistics to match "copy + delete" + procCallback_.updateProcessedData(statBefore.first - statAfter.first, statBefore.second - statAfter.second); + } + } + } + break; + case SO_MOVE_LEFT_TARGET: //it's enough to try each move-pair *once* + case SO_MOVE_RIGHT_TARGET: // + case SO_DELETE_LEFT: + case SO_DELETE_RIGHT: + case SO_OVERWRITE_LEFT: + case SO_OVERWRITE_RIGHT: + case SO_CREATE_NEW_LEFT: + case SO_CREATE_NEW_RIGHT: + case SO_DO_NOTHING: + case SO_EQUAL: + case SO_UNRESOLVED_CONFLICT: + case SO_COPY_METADATA_TO_LEFT: + case SO_COPY_METADATA_TO_RIGHT: + break; + } + } + + std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(), + [&](DirMapping& dirObj) { this->runZeroPass(dirObj); /*recurse */ }); +} +//--------------------------------------------------------------------------------------------------------------- + + +//1st, 2nd pass requirements: +// - avoid disk space shortage: 1. delete files, 2. overwrite big with small files first +// - support change in type: overwrite file by directory, symlink by file, ect. + +inline +SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const FileMapping& fileObj) +{ + switch (fileObj.getSyncOperation()) //evaluate comparison result and sync direction + { + case SO_DELETE_LEFT: + case SO_DELETE_RIGHT: + return PASS_ONE; + + case SO_OVERWRITE_LEFT: + return fileObj.getFileSize<LEFT_SIDE>() > fileObj.getFileSize<RIGHT_SIDE>() ? PASS_ONE : PASS_TWO; + + case SO_OVERWRITE_RIGHT: + return fileObj.getFileSize<LEFT_SIDE>() < fileObj.getFileSize<RIGHT_SIDE>() ? PASS_ONE : PASS_TWO; + + case SO_MOVE_LEFT_SOURCE: // + case SO_MOVE_RIGHT_SOURCE: // [!] + return PASS_NEVER; + case SO_MOVE_LEFT_TARGET: // + case SO_MOVE_RIGHT_TARGET: //to be processed in second pass, after "move target" parent directory was created! + return PASS_TWO; + + case SO_CREATE_NEW_LEFT: + case SO_CREATE_NEW_RIGHT: + case SO_DO_NOTHING: + case SO_EQUAL: + case SO_UNRESOLVED_CONFLICT: + case SO_COPY_METADATA_TO_LEFT: + case SO_COPY_METADATA_TO_RIGHT: + return PASS_TWO; + } + assert(false); + return PASS_TWO; //dummy +} + + +inline +SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const SymLinkMapping& linkObj) +{ + switch (linkObj.getSyncOperation()) //evaluate comparison result and sync direction + { + case SO_DELETE_LEFT: + case SO_DELETE_RIGHT: + return PASS_ONE; //make sure to delete symlinks in first pass, and equally named file or dir in second pass: usecase "overwrite symlink with regular file"! + + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_TARGET: + assert(false); + case SO_OVERWRITE_LEFT: + case SO_OVERWRITE_RIGHT: + case SO_CREATE_NEW_LEFT: + case SO_CREATE_NEW_RIGHT: + case SO_DO_NOTHING: + case SO_EQUAL: + case SO_UNRESOLVED_CONFLICT: + case SO_COPY_METADATA_TO_LEFT: + case SO_COPY_METADATA_TO_RIGHT: + return PASS_TWO; + } + assert(false); + return PASS_TWO; //dummy +} + + +inline +SynchronizeFolderPair::PassId SynchronizeFolderPair::getPass(const DirMapping& dirObj) +{ + switch (dirObj.getSyncOperation()) //evaluate comparison result and sync direction + { + case SO_DELETE_LEFT: + case SO_DELETE_RIGHT: + return PASS_ONE; + + case SO_OVERWRITE_LEFT: + case SO_OVERWRITE_RIGHT: + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_TARGET: + assert(false); + case SO_CREATE_NEW_LEFT: + case SO_CREATE_NEW_RIGHT: + case SO_DO_NOTHING: + case SO_EQUAL: + case SO_UNRESOLVED_CONFLICT: + case SO_COPY_METADATA_TO_LEFT: + case SO_COPY_METADATA_TO_RIGHT: + return PASS_TWO; + } + assert(false); + return PASS_TWO; //dummy +} template <SynchronizeFolderPair::PassId pass> -void SynchronizeFolderPair::execute(HierarchyObject& hierObj) +void SynchronizeFolderPair::runPass(HierarchyObject& hierObj) { //synchronize files: std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(), [&](FileMapping& fileObj) { - const bool letsDoThis = (pass == FIRST_PASS) == diskSpaceIsReduced(fileObj); //to be deleted files on first pass, rest on second! - if (letsDoThis) - tryReportingError(procCallback_, [&]() { synchronizeFile(fileObj); }); + if (pass == getPass(fileObj)) + tryReportingError(procCallback_, [&] { synchronizeFile(fileObj); }); }); - //synchronize symbolic links: (process in second step only) - if (pass == SECOND_PASS) - std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), - [&](SymLinkMapping& linkObj) { tryReportingError(procCallback_, [&]() { synchronizeLink(linkObj); }); }); + //synchronize symbolic links: + std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), + [&](SymLinkMapping& linkObj) + { + if (pass == getPass(linkObj)) + tryReportingError(procCallback_, [&] { synchronizeLink(linkObj); }); + }); //synchronize folders: std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(), [&](DirMapping& dirObj) { - const bool letsDoThis = (pass == FIRST_PASS) == diskSpaceIsReduced(dirObj); //to be deleted dirs on first pass, rest on second! - if (letsDoThis) //if we created all folders on first pass it would look strange for initial mirror sync when all folders are created at once - tryReportingError(procCallback_, [&]() { synchronizeFolder(dirObj); }); + if (pass == getPass(dirObj)) + tryReportingError(procCallback_, [&] { synchronizeFolder(dirObj); }); - //recursion! - this->execute<pass>(dirObj); + this->runPass<pass>(dirObj); //recurse }); } +//--------------------------------------------------------------------------------------------------------------- -void SynchronizeFolderPair::synchronizeFile(FileMapping& fileObj) const +namespace { - wxString logText; - Zstring target; - - switch (fileObj.getSyncOperation()) //evaluate comparison result and sync direction +inline +bool getTargetDir(SyncOperation syncOp, SelectedSide* side) +{ + switch (syncOp) { case SO_CREATE_NEW_LEFT: - target = fileObj.getBaseDirPf<LEFT_SIDE>() + fileObj.getRelativeName<RIGHT_SIDE>(); //can't use "getFullName" as target is not yet existing + case SO_DELETE_LEFT: + case SO_OVERWRITE_LEFT: + case SO_COPY_METADATA_TO_LEFT: + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_LEFT_TARGET: + *side = LEFT_SIDE; + return true; - logText = txtCreatingFile; - replace(logText, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); + case SO_CREATE_NEW_RIGHT: + case SO_DELETE_RIGHT: + case SO_OVERWRITE_RIGHT: + case SO_COPY_METADATA_TO_RIGHT: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_RIGHT_TARGET: + *side = RIGHT_SIDE; + return true; - try - { - FileDescriptor sourceAttr; - copyFileUpdatingTo<LEFT_SIDE>(fileObj, - []() {}, //no target to delete - sourceAttr); + case SO_DO_NOTHING: + case SO_EQUAL: + case SO_UNRESOLVED_CONFLICT: + break; //nothing to do + } + return false; +} +} - fileObj.copyTo<LEFT_SIDE>(&sourceAttr); //update FileMapping - } - catch (FileError&) - { - if (fileExists(fileObj.getFullName<RIGHT_SIDE>())) - throw; - //source deleted meanwhile... - procCallback_.updateProcessedData(0, to<zen::Int64>(fileObj.getFileSize<RIGHT_SIDE>())); - fileObj.removeObject<RIGHT_SIDE>(); - } - break; +inline +void SynchronizeFolderPair::synchronizeFile(FileMapping& fileObj) const +{ + const SyncOperation syncOp = fileObj.getSyncOperation(); + + SelectedSide sideTrg = LEFT_SIDE; + + if (getTargetDir(syncOp, &sideTrg)) + { + if (sideTrg == LEFT_SIDE) + synchronizeFileInt<LEFT_SIDE>(fileObj, syncOp); + else + synchronizeFileInt<RIGHT_SIDE>(fileObj, syncOp); + } +} + + +template <SelectedSide sideTrg> +void SynchronizeFolderPair::synchronizeFileInt(FileMapping& fileObj, SyncOperation syncOp) const +{ + static const SelectedSide sideSrc = OtherSide<sideTrg>::result; + + switch (syncOp) + { + case SO_CREATE_NEW_LEFT: case SO_CREATE_NEW_RIGHT: - target = fileObj.getBaseDirPf<RIGHT_SIDE>() + fileObj.getRelativeName<LEFT_SIDE>(); + { + const Zstring& target = fileObj.getBaseDirPf<sideTrg>() + fileObj.getRelativeName<sideSrc>(); //can't use "getFullName" as target is not yet existing - logText = txtCreatingFile; - replace(logText, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); + reportInfo(txtCreatingFile, target); try { - FileDescriptor sourceAttr; - copyFileUpdatingTo<RIGHT_SIDE>(fileObj, - []() {}, //no target to delete - sourceAttr); + FileAttrib newAttr; + copyFileUpdatingTo<sideTrg>(fileObj, [] {}, /*no target to delete*/ newAttr); - fileObj.copyTo<RIGHT_SIDE>(&sourceAttr); //update FileMapping + const FileDescriptor descrSource(newAttr.modificationTime, newAttr.fileSize, newAttr.sourceFileId); + const FileDescriptor descrTarget(newAttr.modificationTime, newAttr.fileSize, newAttr.targetFileId); + fileObj.syncTo<sideTrg>(descrTarget, &descrSource); //update FileMapping } catch (FileError&) { - if (fileExists(fileObj.getFullName<LEFT_SIDE>())) + if (fileExists(fileObj.getFullName<sideSrc>())) throw; //source deleted meanwhile... - procCallback_.updateProcessedData(0, to<zen::Int64>(fileObj.getFileSize<LEFT_SIDE>())); - fileObj.removeObject<LEFT_SIDE>(); + procCallback_.updateProcessedData(0, to<zen::Int64>(fileObj.getFileSize<sideSrc>())); + fileObj.removeObject<sideSrc>(); } - break; + } + break; case SO_DELETE_LEFT: - logText = replaceCpy(delHandlingLeft_.getTxtRemovingFile(), L"%x", utf8CvrtTo<wxString>(fileObj.getFullName<LEFT_SIDE>())); - procCallback_.reportInfo(logText); + case SO_DELETE_RIGHT: + reportInfo(getDelHandling<sideTrg>().getTxtRemovingFile(), fileObj.getFullName<sideTrg>()); - delHandlingLeft_.removeFile(fileObj.getObjRelativeName()); //throw FileError - fileObj.removeObject<LEFT_SIDE>(); //update FileMapping + getDelHandling<sideTrg>().removeFile(fileObj.getObjRelativeName()); //throw FileError + fileObj.removeObject<sideTrg>(); //update FileMapping break; - case SO_DELETE_RIGHT: - logText = replaceCpy(delHandlingRight_.getTxtRemovingFile(), L"%x", utf8CvrtTo<wxString>(fileObj.getFullName<RIGHT_SIDE>())); - procCallback_.reportInfo(logText); + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_RIGHT_TARGET: + if (FileMapping* targetObj = dynamic_cast<FileMapping*>(FileSystemObject::retrieve(fileObj.getMoveRef()))) + { + FileMapping* sourceObj = &fileObj; - delHandlingRight_.removeFile(fileObj.getObjRelativeName()); //throw FileError - fileObj.removeObject<RIGHT_SIDE>(); //update FileMapping - break; + if (syncOp != SO_MOVE_LEFT_SOURCE && syncOp != SO_MOVE_RIGHT_SOURCE) + std::swap(sourceObj, targetObj); - case SO_OVERWRITE_LEFT: - { - target = fileObj.getBaseDirPf<LEFT_SIDE>() + fileObj.getRelativeName<RIGHT_SIDE>(); //respect differences in case of source object + assert((sourceObj->getSyncOperation() == SO_MOVE_LEFT_SOURCE && targetObj->getSyncOperation() == SO_MOVE_LEFT_TARGET && sideTrg == LEFT_SIDE) || + (sourceObj->getSyncOperation() == SO_MOVE_RIGHT_SOURCE && targetObj->getSyncOperation() == SO_MOVE_RIGHT_TARGET && sideTrg == RIGHT_SIDE)); - logText = txtOverwritingFile; - replace(logText, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); + const Zstring& source = sourceObj->getFullName<sideTrg>(); + const Zstring& target = targetObj->getBaseDirPf<sideTrg>() + targetObj->getRelativeName<sideSrc>(); - FileDescriptor sourceAttr; - copyFileUpdatingTo<LEFT_SIDE>(fileObj, - [&]() //delete target at appropriate time - { - procCallback_.reportStatus(replaceCpy(delHandlingLeft_.getTxtRemovingFile(), L"%x", utf8CvrtTo<wxString>(fileObj.getFullName<LEFT_SIDE>()))); + reportInfo(replaceCpy(txtMovingFile, L"%y", utf8CvrtTo<std::wstring>(target)), source); - delHandlingLeft_.removeFile(fileObj.getObjRelativeName()); //throw FileError - fileObj.removeObject<LEFT_SIDE>(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) + renameFile(source, target); //throw FileError; - procCallback_.reportStatus(logText); //restore status text copy file - }, sourceAttr); + const FileDescriptor descrTarget(sourceObj->getLastWriteTime<sideTrg>(), + sourceObj->getFileSize <sideTrg>(), + sourceObj->getFileId <sideTrg>()); - fileObj.copyTo<LEFT_SIDE>(&sourceAttr); //update FileMapping - } - break; + sourceObj->removeObject<sideTrg>(); // + targetObj->syncTo<sideTrg>(descrTarget); //update FileMapping + } + break; + case SO_OVERWRITE_LEFT: case SO_OVERWRITE_RIGHT: { - target = fileObj.getBaseDirPf<RIGHT_SIDE>() + fileObj.getRelativeName<LEFT_SIDE>(); //respect differences in case of source object + const Zstring& target = fileObj.getBaseDirPf<sideTrg>() + fileObj.getRelativeName<sideSrc>(); //respect differences in case of source object - logText = txtOverwritingFile; - replace(logText, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); + reportInfo(txtOverwritingFile, target); - FileDescriptor sourceAttr; - copyFileUpdatingTo<RIGHT_SIDE>(fileObj, - [&]() //delete target at appropriate time + FileAttrib newAttr; + copyFileUpdatingTo<sideTrg>(fileObj, + [&] //delete target at appropriate time { - procCallback_.reportStatus(replaceCpy(delHandlingRight_.getTxtRemovingFile(), L"%x", utf8CvrtTo<wxString>(fileObj.getFullName<RIGHT_SIDE>()))); + reportStatus(this->getDelHandling<sideTrg>().getTxtRemovingFile(), fileObj.getFullName<sideTrg>()); - delHandlingRight_.removeFile(fileObj.getObjRelativeName()); //throw FileError - fileObj.removeObject<RIGHT_SIDE>(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) + this->getDelHandling<sideTrg>().removeFile(fileObj.getObjRelativeName()); //throw FileError + fileObj.removeObject<sideTrg>(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) - procCallback_.reportStatus(logText); //restore status text copy file - }, sourceAttr); + reportStatus(txtOverwritingFile, target); //restore status text copy file + }, newAttr); - fileObj.copyTo<RIGHT_SIDE>(&sourceAttr); //update FileMapping + const FileDescriptor descrSource(newAttr.modificationTime, newAttr.fileSize, newAttr.sourceFileId); + const FileDescriptor descrTarget(newAttr.modificationTime, newAttr.fileSize, newAttr.targetFileId); + fileObj.syncTo<sideTrg>(descrTarget, &descrSource); //update FileMapping } break; case SO_COPY_METADATA_TO_LEFT: - logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo<wxString>(fileObj.getFullName<LEFT_SIDE>())); - procCallback_.reportInfo(logText); - - if (fileObj.getShortName<LEFT_SIDE>() != fileObj.getShortName<RIGHT_SIDE>()) //adapt difference in case (windows only) - renameFile(fileObj.getFullName<LEFT_SIDE>(), - beforeLast(fileObj.getFullName<LEFT_SIDE>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName<RIGHT_SIDE>()); //throw FileError; - - if (!sameFileTime(fileObj.getLastWriteTime<LEFT_SIDE>(), fileObj.getLastWriteTime<RIGHT_SIDE>(), 2)) //respect 2 second FAT/FAT32 precision - setFileTime(fileObj.getFullName<LEFT_SIDE>(), fileObj.getLastWriteTime<RIGHT_SIDE>(), SYMLINK_FOLLOW); //throw FileError - //do NOT read *current* source file time, but use buffered value which corresponds to time of comparison! - - fileObj.copyTo<LEFT_SIDE>(NULL); //-> both sides *should* be completely equal now... - break; - case SO_COPY_METADATA_TO_RIGHT: - logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo<wxString>(fileObj.getFullName<RIGHT_SIDE>())); - procCallback_.reportInfo(logText); + { + reportInfo(txtWritingAttributes, fileObj.getFullName<sideTrg>()); - if (fileObj.getShortName<LEFT_SIDE>() != fileObj.getShortName<RIGHT_SIDE>()) //adapt difference in case (windows only) - renameFile(fileObj.getFullName<RIGHT_SIDE>(), - beforeLast(fileObj.getFullName<RIGHT_SIDE>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileObj.getShortName<LEFT_SIDE>()); //throw FileError; + 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; - if (!sameFileTime(fileObj.getLastWriteTime<LEFT_SIDE>(), fileObj.getLastWriteTime<RIGHT_SIDE>(), 2)) //respect 2 second FAT/FAT32 precision - setFileTime(fileObj.getFullName<RIGHT_SIDE>(), fileObj.getLastWriteTime<LEFT_SIDE>(), SYMLINK_FOLLOW); //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 + //do NOT read *current* source file time, but use buffered value which corresponds to time of comparison! - fileObj.copyTo<RIGHT_SIDE>(NULL); //-> both sides *should* be completely equal now... - break; + const FileDescriptor descrTarget(fileObj.getLastWriteTime<sideSrc>(), + fileObj.getFileSize <sideTrg >(), + fileObj.getFileId <sideTrg >()); + fileObj.syncTo<sideTrg>(descrTarget); //-> both sides *should* be completely equal now... + } + break; case SO_DO_NOTHING: case SO_EQUAL: @@ -1049,137 +1416,112 @@ void SynchronizeFolderPair::synchronizeFile(FileMapping& fileObj) const } +inline void SynchronizeFolderPair::synchronizeLink(SymLinkMapping& linkObj) const { - wxString logText; - Zstring target; + const SyncOperation syncOp = linkObj.getSyncOperation(); - switch (linkObj.getSyncOperation()) //evaluate comparison result and sync direction + SelectedSide sideTrg = LEFT_SIDE; + + if (getTargetDir(syncOp, &sideTrg)) { - case SO_CREATE_NEW_LEFT: - target = linkObj.getBaseDirPf<LEFT_SIDE>() + linkObj.getRelativeName<RIGHT_SIDE>(); + if (sideTrg == LEFT_SIDE) + synchronizeLinkInt<LEFT_SIDE>(linkObj, syncOp); + else + synchronizeLinkInt<RIGHT_SIDE>(linkObj, syncOp); + } +} - logText = txtCreatingLink; - replace(logText, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); - try - { - zen::copySymlink(linkObj.getFullName<RIGHT_SIDE>(), target, copyFilePermissions); //throw FileError +template <SelectedSide sideTrg> +void SynchronizeFolderPair::synchronizeLinkInt(SymLinkMapping& linkObj, SyncOperation syncOp) const +{ + static const SelectedSide sideSrc = OtherSide<sideTrg>::result; - linkObj.copyTo<LEFT_SIDE>(); //update SymLinkMapping - } - catch (FileError&) - { - if (fileExists(linkObj.getFullName<RIGHT_SIDE>())) - throw; - //source deleted meanwhile... - linkObj.removeObject<RIGHT_SIDE>(); - } - break; + auto deleteSymlink = [&] + { + switch (linkObj.getLinkType<sideTrg>()) + { + case LinkDescriptor::TYPE_DIR: + this->getDelHandling<sideTrg>().removeFolder(linkObj.getObjRelativeName()); //throw FileError + break; + + case LinkDescriptor::TYPE_FILE: //Windows: true file symlink; Linux: file-link or broken link + this->getDelHandling<sideTrg>().removeFile(linkObj.getObjRelativeName()); //throw FileError + break; + } + }; + switch (syncOp) + { + case SO_CREATE_NEW_LEFT: case SO_CREATE_NEW_RIGHT: - target = linkObj.getBaseDirPf<RIGHT_SIDE>() + linkObj.getRelativeName<LEFT_SIDE>(); + { + const Zstring& target = linkObj.getBaseDirPf<sideTrg>() + linkObj.getRelativeName<sideSrc>(); - logText = txtCreatingLink; - replace(logText, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); + reportInfo(txtCreatingLink, target); try { - zen::copySymlink(linkObj.getFullName<LEFT_SIDE>(), target, copyFilePermissions); //throw FileError + zen::copySymlink(linkObj.getFullName<sideSrc>(), target, copyFilePermissions); //throw FileError - linkObj.copyTo<RIGHT_SIDE>(); //update SymLinkMapping + linkObj.copyTo<sideTrg>(); //update SymLinkMapping } catch (FileError&) { - if (fileExists(linkObj.getFullName<LEFT_SIDE>())) + if (fileExists(linkObj.getFullName<sideSrc>())) throw; //source deleted meanwhile... - linkObj.removeObject<LEFT_SIDE>(); + linkObj.removeObject<sideSrc>(); } - break; + } + break; case SO_DELETE_LEFT: - logText = replaceCpy(delHandlingLeft_.getTxtRemovingSymLink(), L"%x", utf8CvrtTo<wxString>(linkObj.getFullName<LEFT_SIDE>())); - procCallback_.reportInfo(logText); - - deleteSymlink<LEFT_SIDE>(linkObj); //throw FileError - - linkObj.removeObject<LEFT_SIDE>(); //update SymLinkMapping - break; - case SO_DELETE_RIGHT: - logText = replaceCpy(delHandlingRight_.getTxtRemovingSymLink(), L"%x", utf8CvrtTo<wxString>(linkObj.getFullName<RIGHT_SIDE>())); - procCallback_.reportInfo(logText); + reportInfo(getDelHandling<sideTrg>().getTxtRemovingSymLink(), linkObj.getFullName<sideTrg>()); - deleteSymlink<RIGHT_SIDE>(linkObj); //throw FileError + deleteSymlink(); //throw FileError - linkObj.removeObject<RIGHT_SIDE>(); //update SymLinkMapping + linkObj.removeObject<sideTrg>(); //update SymLinkMapping break; case SO_OVERWRITE_LEFT: - target = linkObj.getBaseDirPf<LEFT_SIDE>() + linkObj.getRelativeName<RIGHT_SIDE>(); //respect differences in case of source object - - logText = txtOverwritingLink; - replace(logText, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); - - procCallback_.reportStatus(replaceCpy(delHandlingLeft_.getTxtRemovingSymLink(), L"%x", utf8CvrtTo<wxString>(linkObj.getFullName<LEFT_SIDE>()))); - deleteSymlink<LEFT_SIDE>(linkObj); //throw FileError - linkObj.removeObject<LEFT_SIDE>(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) - - procCallback_.reportStatus(logText); //restore status text - zen::copySymlink(linkObj.getFullName<RIGHT_SIDE>(), target, copyFilePermissions); //throw FileError - - linkObj.copyTo<LEFT_SIDE>(); //update SymLinkMapping - break; - case SO_OVERWRITE_RIGHT: - target = linkObj.getBaseDirPf<RIGHT_SIDE>() + linkObj.getRelativeName<LEFT_SIDE>(); //respect differences in case of source object - - logText = txtOverwritingLink; - replace(logText, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); + { + const Zstring& target = linkObj.getBaseDirPf<sideTrg>() + linkObj.getRelativeName<sideSrc>(); //respect differences in case of source object - procCallback_.reportStatus(replaceCpy(delHandlingRight_.getTxtRemovingSymLink(), L"%x", utf8CvrtTo<wxString>(linkObj.getFullName<RIGHT_SIDE>()))); - deleteSymlink<RIGHT_SIDE>(linkObj); //throw FileError - linkObj.removeObject<RIGHT_SIDE>(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) + reportInfo(txtOverwritingLink, target); - procCallback_.reportStatus(logText); //restore status text - zen::copySymlink(linkObj.getFullName<LEFT_SIDE>(), target, copyFilePermissions); //throw FileError + reportStatus(getDelHandling<sideTrg>().getTxtRemovingSymLink(), linkObj.getFullName<sideTrg>()); + deleteSymlink(); //throw FileError + linkObj.removeObject<sideTrg>(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) - linkObj.copyTo<RIGHT_SIDE>(); //update SymLinkMapping - break; + reportStatus(txtOverwritingLink, target); //restore status text + zen::copySymlink(linkObj.getFullName<sideSrc>(), target, copyFilePermissions); //throw FileError + linkObj.copyTo<sideTrg>(); //update SymLinkMapping + } + break; case SO_COPY_METADATA_TO_LEFT: - logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo<wxString>(linkObj.getFullName<LEFT_SIDE>())); - procCallback_.reportInfo(logText); - - if (linkObj.getShortName<LEFT_SIDE>() != linkObj.getShortName<RIGHT_SIDE>()) //adapt difference in case (windows only) - renameFile(linkObj.getFullName<LEFT_SIDE>(), - beforeLast(linkObj.getFullName<LEFT_SIDE>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + linkObj.getShortName<RIGHT_SIDE>()); //throw FileError; - - if (!sameFileTime(linkObj.getLastWriteTime<LEFT_SIDE>(), linkObj.getLastWriteTime<RIGHT_SIDE>(), 2)) //respect 2 second FAT/FAT32 precision - setFileTime(linkObj.getFullName<LEFT_SIDE>(), linkObj.getLastWriteTime<RIGHT_SIDE>(), SYMLINK_DIRECT); //throw FileError - - linkObj.copyTo<LEFT_SIDE>(); //-> both sides *should* be completely equal now... - break; - case SO_COPY_METADATA_TO_RIGHT: - logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo<wxString>(linkObj.getFullName<RIGHT_SIDE>())); - procCallback_.reportInfo(logText); + reportInfo(txtWritingAttributes, linkObj.getFullName<sideTrg>()); - if (linkObj.getShortName<LEFT_SIDE>() != linkObj.getShortName<RIGHT_SIDE>()) //adapt difference in case (windows only) - renameFile(linkObj.getFullName<RIGHT_SIDE>(), - beforeLast(linkObj.getFullName<RIGHT_SIDE>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + linkObj.getShortName<LEFT_SIDE>()); //throw FileError; + 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; - if (!sameFileTime(linkObj.getLastWriteTime<LEFT_SIDE>(), linkObj.getLastWriteTime<RIGHT_SIDE>(), 2)) //respect 2 second FAT/FAT32 precision - setFileTime(linkObj.getFullName<RIGHT_SIDE>(), linkObj.getLastWriteTime<LEFT_SIDE>(), SYMLINK_DIRECT); //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 - linkObj.copyTo<RIGHT_SIDE>(); //-> both sides *should* be completely equal now... + linkObj.copyTo<sideTrg>(); //-> both sides *should* be completely equal now... break; + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_TARGET: + assert(false); case SO_DO_NOTHING: case SO_EQUAL: case SO_UNRESOLVED_CONFLICT: @@ -1193,126 +1535,89 @@ void SynchronizeFolderPair::synchronizeLink(SymLinkMapping& linkObj) const } +inline void SynchronizeFolderPair::synchronizeFolder(DirMapping& dirObj) const { - wxString logText; - Zstring target; + const SyncOperation syncOp = dirObj.getSyncOperation(); - //synchronize folders: - switch (dirObj.getSyncOperation()) //evaluate comparison result and sync direction - { - case SO_CREATE_NEW_LEFT: - //some check to catch the error that directory on source has been deleted externally after "compare"... - if (!dirExists(dirObj.getFullName<RIGHT_SIDE>())) - { - // throw FileError(_ ("Source directory does not exist anymore:") + "\n\"" + dirObj.getFullName<RIGHT_SIDE>() + "\""); - const SyncStatistics subObjects(dirObj); //DON'T forget to notify about implicitly deleted objects! - procCallback_.updateProcessedData(subObjects.getCreate() + subObjects.getOverwrite() + subObjects.getDelete(), to<zen::Int64>(subObjects.getDataToProcess())); + SelectedSide sideTrg = LEFT_SIDE; - dirObj.refSubFiles().clear(); //...then remove sub-objects - dirObj.refSubLinks().clear(); // - dirObj.refSubDirs ().clear(); // - dirObj.removeObject<RIGHT_SIDE>(); - } - else - { - target = dirObj.getBaseDirPf<LEFT_SIDE>() + dirObj.getRelativeName<RIGHT_SIDE>(); + if (getTargetDir(syncOp, &sideTrg)) + { + if (sideTrg == LEFT_SIDE) + synchronizeFolderInt<LEFT_SIDE>(dirObj, syncOp); + else + synchronizeFolderInt<RIGHT_SIDE>(dirObj, syncOp); + } +} - logText = replaceCpy(txtCreatingFolder, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); - createDirectory(target, dirObj.getFullName<RIGHT_SIDE>(), copyFilePermissions); //no symlink copying! - dirObj.copyTo<LEFT_SIDE>(); //update DirMapping - } - break; +template <SelectedSide sideTrg> +void SynchronizeFolderPair::synchronizeFolderInt(DirMapping& dirObj, SyncOperation syncOp) const +{ + static const SelectedSide sideSrc = OtherSide<sideTrg>::result; + switch (syncOp) + { + case SO_CREATE_NEW_LEFT: case SO_CREATE_NEW_RIGHT: - //some check to catch the error that directory on source has been deleted externally after "compare"... - if (!dirExists(dirObj.getFullName<LEFT_SIDE>())) + if (dirExists(dirObj.getFullName<sideSrc>())) { - //throw FileError(_ ("Source directory does not exist anymore:") + "\n\"" + dirObj.getFullName<LEFT_SIDE>() + "\""); - const SyncStatistics subObjects(dirObj); //DON'T forget to notify about implicitly deleted objects! - procCallback_.updateProcessedData(subObjects.getCreate() + subObjects.getOverwrite() + subObjects.getDelete(), to<zen::Int64>(subObjects.getDataToProcess())); + const Zstring& target = dirObj.getBaseDirPf<sideTrg>() + dirObj.getRelativeName<sideSrc>(); - dirObj.refSubFiles().clear(); //...then remove sub-objects - dirObj.refSubLinks().clear(); // - dirObj.refSubDirs ().clear(); // - dirObj.removeObject<LEFT_SIDE>(); + reportInfo(txtCreatingFolder, target); + createDirectory(target, dirObj.getFullName<sideSrc>(), copyFilePermissions); //no symlink copying! + dirObj.copyTo<sideTrg>(); //update DirMapping } - else + else //source directory has been deleted externally after "compare"... { - target = dirObj.getBaseDirPf<RIGHT_SIDE>() + dirObj.getRelativeName<LEFT_SIDE>(); - - logText = replaceCpy(txtCreatingFolder, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); + // throw FileError(_ ("Source directory does not exist anymore:") + "\n\"" + dirObj.getFullName<sideSrc>() + "\""); + const SyncStatistics subStats(dirObj); //DON'T forget to notify about implicitly deleted objects! + procCallback_.updateProcessedData(getCUD(subStats), to<zen::Int64>(subStats.getDataToProcess())); - createDirectory(target, dirObj.getFullName<LEFT_SIDE>(), copyFilePermissions); //no symlink copying! - dirObj.copyTo<RIGHT_SIDE>(); //update DirMapping + dirObj.refSubFiles().clear(); //...then remove sub-objects + dirObj.refSubLinks().clear(); // + dirObj.refSubDirs ().clear(); // + dirObj.removeObject<sideSrc>(); } break; case SO_COPY_METADATA_TO_LEFT: - logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo<wxString>(dirObj.getFullName<LEFT_SIDE>())); - procCallback_.reportInfo(logText); - - if (dirObj.getShortName<LEFT_SIDE>() != dirObj.getShortName<RIGHT_SIDE>()) //adapt difference in case (windows only) - renameFile(dirObj.getFullName<LEFT_SIDE>(), - beforeLast(dirObj.getFullName<LEFT_SIDE>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + dirObj.getShortName<RIGHT_SIDE>()); //throw FileError; - //copyFileTimes(dirObj.getFullName<RIGHT_SIDE>(), dirObj.getFullName<LEFT_SIDE>(), true); //throw FileError -> is executed after sub-objects have finished synchronization - - dirObj.copyTo<LEFT_SIDE>(); //-> both sides *should* be completely equal now... - break; - case SO_COPY_METADATA_TO_RIGHT: - logText = replaceCpy(txtWritingAttributes, L"%x", utf8CvrtTo<wxString>(dirObj.getFullName<RIGHT_SIDE>())); - procCallback_.reportInfo(logText); + reportInfo(txtWritingAttributes, dirObj.getFullName<sideTrg>()); - if (dirObj.getShortName<LEFT_SIDE>() != dirObj.getShortName<RIGHT_SIDE>()) //adapt difference in case (windows only) - renameFile(dirObj.getFullName<RIGHT_SIDE>(), - beforeLast(dirObj.getFullName<RIGHT_SIDE>(), FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + dirObj.getShortName<LEFT_SIDE>()); //throw FileError; - //copyFileTimes(dirObj.getFullName<LEFT_SIDE>(), dirObj.getFullName<RIGHT_SIDE>(), true); //throw FileError -> is executed after sub-objects have finished synchronization + 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; + //copyFileTimes -> useless at this time: modification time changes with each child-object creation/deletion - dirObj.copyTo<RIGHT_SIDE>(); //-> both sides *should* be completely equal now... + dirObj.copyTo<sideTrg>(); //-> both sides *should* be completely equal now... break; case SO_DELETE_LEFT: - //status information - logText = replaceCpy(delHandlingLeft_.getTxtRemovingDir(), L"%x", utf8CvrtTo<wxString>(dirObj.getFullName<LEFT_SIDE>())); - procCallback_.reportInfo(logText); + case SO_DELETE_RIGHT: + { + reportInfo(getDelHandling<sideTrg>().getTxtRemovingDir(), dirObj.getFullName<sideTrg>()); - delHandlingLeft_.removeFolder(dirObj.getObjRelativeName()); //throw FileError - { - //progress indicator update: DON'T forget to notify about implicitly deleted objects! - const SyncStatistics subObjects(dirObj); - //...then remove everything - dirObj.refSubFiles().clear(); - dirObj.refSubLinks().clear(); - dirObj.refSubDirs ().clear(); - procCallback_.updateProcessedData(subObjects.getCreate() + subObjects.getOverwrite() + subObjects.getDelete(), to<zen::Int64>(subObjects.getDataToProcess())); - } - dirObj.removeObject<LEFT_SIDE>(); //update DirMapping - break; + getDelHandling<sideTrg>().removeFolder(dirObj.getObjRelativeName()); //throw FileError - case SO_DELETE_RIGHT: - //status information - logText = replaceCpy(delHandlingRight_.getTxtRemovingDir(), L"%x", utf8CvrtTo<wxString>(dirObj.getFullName<RIGHT_SIDE>())); - procCallback_.reportInfo(logText); + //progress indicator update: DON'T forget to notify about implicitly deleted objects! + const SyncStatistics subStats(dirObj); + dirObj.refSubFiles().clear(); //...then remove everything + dirObj.refSubLinks().clear(); + dirObj.refSubDirs ().clear(); + procCallback_.updateProcessedData(getCUD(subStats), to<zen::Int64>(subStats.getDataToProcess())); - delHandlingRight_.removeFolder(dirObj.getObjRelativeName()); //throw FileError - { - //progress indicator update: DON'T forget to notify about implicitly deleted objects! - const SyncStatistics subObjects(dirObj); - //...then remove everything - dirObj.refSubFiles().clear(); - dirObj.refSubLinks().clear(); - dirObj.refSubDirs ().clear(); - procCallback_.updateProcessedData(subObjects.getCreate() + subObjects.getOverwrite() + subObjects.getDelete(), to<zen::Int64>(subObjects.getDataToProcess())); - } - dirObj.removeObject<RIGHT_SIDE>(); //update DirMapping - break; + dirObj.removeObject<sideTrg>(); //update DirMapping + } + break; case SO_OVERWRITE_RIGHT: case SO_OVERWRITE_LEFT: + case SO_MOVE_LEFT_SOURCE: + case SO_MOVE_RIGHT_SOURCE: + case SO_MOVE_LEFT_TARGET: + case SO_MOVE_RIGHT_TARGET: assert(false); case SO_UNRESOLVED_CONFLICT: case SO_DO_NOTHING: @@ -1336,31 +1641,27 @@ void makeSameLength(std::wstring& first, std::wstring& second) second.resize(maxPref, L' '); } + +/* struct LessDependentDirectory : public std::binary_function<Zstring, Zstring, bool> { +-> a *very* bad idea: this is NOT a strict weak ordering! No transitivity of equivalence! + bool operator()(const Zstring& lhs, const Zstring& rhs) const { return LessFilename()(Zstring(lhs.c_str(), std::min(lhs.length(), rhs.length())), Zstring(rhs.c_str(), std::min(lhs.length(), rhs.length()))); } }; - -struct EqualDependentDirectory : public std::binary_function<Zstring, Zstring, bool> -{ - bool operator()(const Zstring& lhs, const Zstring& rhs) const - { - return EqualFilename()(Zstring(lhs.c_str(), std::min(lhs.length(), rhs.length())), - Zstring(rhs.c_str(), std::min(lhs.length(), rhs.length()))); - } -}; +*/ } void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCfg>& syncConfig, FolderComparison& folderCmp) { - //prevent shutdown while synchronization is in progress - DisableStandby dummy; - (void)dummy; + //prevent shutdown while synchronization is in progress + PreventStandby dummy; + (void)dummy; #ifdef NDEBUG wxLogNull noWxLogs; //prevent wxWidgets logging @@ -1371,12 +1672,11 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf if (syncConfig.size() != folderCmp.size()) throw std::logic_error("Programming Error: Contract violation!"); - //inform about the total amount of data that will be processed from now on const SyncStatistics statisticsTotal(folderCmp); //keep at beginning so that all gui elements are initialized properly - procCallback.initNewProcess(statisticsTotal.getCreate() + statisticsTotal.getOverwrite() + statisticsTotal.getDelete(), + procCallback.initNewProcess(getCUD(statisticsTotal), to<zen::Int64>(statisticsTotal.getDataToProcess()), ProcessCallback::PROCESS_SYNCHRONIZING); @@ -1404,12 +1704,34 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf //-------------------some basic checks:------------------------------------------ + auto dependentDir = [](const Zstring& lhs, const Zstring& rhs) //note: this is NOT an equivalence relation! + { + return EqualFilename()(Zstring(lhs.c_str(), std::min(lhs.length(), rhs.length())), + Zstring(rhs.c_str(), std::min(lhs.length(), rhs.length()))); + }; //aggregate information - typedef std::set<Zstring, LessDependentDirectory> DirReadSet; //count (at least one) read access - typedef std::map<Zstring, size_t, LessDependentDirectory> DirWriteMap; //count (read+)write accesses - DirReadSet dirReadCount; - DirWriteMap dirWriteCount; + std::map<Zstring, std::pair<size_t, size_t>> dirReadWriteCount; //count read/write accesses + auto incReadCount = [&](const Zstring& baseDir) + { + dirReadWriteCount[baseDir]; //create entry + for (auto iter = dirReadWriteCount.begin(); iter != dirReadWriteCount.end(); ++iter) + { + auto& countRef = iter->second; + if (dependentDir(baseDir, iter->first)) + ++countRef.first; + } + }; + auto incWriteCount = [&](const Zstring& baseDir) + { + dirReadWriteCount[baseDir]; //create entry + for (auto iter = dirReadWriteCount.begin(); iter != dirReadWriteCount.end(); ++iter) + { + auto& countRef = iter->second; + if (dependentDir(baseDir, iter->first)) + ++countRef.second; + } + }; typedef std::vector<std::pair<Zstring, Zstring> > DirPairList; DirPairList significantDiff; @@ -1417,8 +1739,7 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf typedef std::vector<std::pair<Zstring, std::pair<zen::Int64, zen::Int64> > > DirSpaceRequAvailList; //dirname / space required / space available DirSpaceRequAvailList diskSpaceMissing; - typedef std::set<Zstring, LessDependentDirectory> DirRecyclerMissing; - DirRecyclerMissing recyclMissing; + std::set<Zstring> recyclMissing; //start checking folder pairs for (auto j = begin(folderCmp); j != end(folderCmp); ++j) @@ -1435,13 +1756,13 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf const SyncStatistics folderPairStat(*j); //aggregate basic information - const bool writeLeft = folderPairStat.getCreate <LEFT_SIDE>() + - folderPairStat.getOverwrite<LEFT_SIDE>() + - folderPairStat.getDelete <LEFT_SIDE>() > 0; + const bool writeLeft = folderPairStat.getCreate<LEFT_SIDE>() + + folderPairStat.getUpdate<LEFT_SIDE>() + + folderPairStat.getDelete<LEFT_SIDE>() > 0; - const bool writeRight = folderPairStat.getCreate <RIGHT_SIDE>() + - folderPairStat.getOverwrite<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 && @@ -1453,7 +1774,7 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf //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)) || + if ((j->getBaseDirPf<LEFT_SIDE >().empty() && (writeLeft || folderPairCfg.inAutomaticMode)) || (j->getBaseDirPf<RIGHT_SIDE>().empty() && (writeRight || folderPairCfg.inAutomaticMode))) { procCallback.reportFatalError(_("Target directory name must not be empty!")); @@ -1462,30 +1783,32 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf } //aggregate information of folders used by multiple pairs in read/write access - if (!EqualDependentDirectory()(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())) //true in general + if (!dependentDir(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())) //true in general { - if (writeLeft) + if (writeLeft && writeRight) + { + incWriteCount(j->getBaseDirPf<LEFT_SIDE >()); + incWriteCount(j->getBaseDirPf<RIGHT_SIDE>()); + } + else if (writeLeft) { - ++dirWriteCount[j->getBaseDirPf<LEFT_SIDE>()]; - if (writeRight) - ++dirWriteCount[j->getBaseDirPf<RIGHT_SIDE>()]; - else - dirReadCount.insert(j->getBaseDirPf<RIGHT_SIDE>()); + incWriteCount(j->getBaseDirPf<LEFT_SIDE>()); + incReadCount (j->getBaseDirPf<RIGHT_SIDE>()); } else if (writeRight) { - dirReadCount.insert(j->getBaseDirPf<LEFT_SIDE>()); - ++dirWriteCount[j->getBaseDirPf<RIGHT_SIDE>()]; + incReadCount (j->getBaseDirPf<LEFT_SIDE>()); + incWriteCount(j->getBaseDirPf<RIGHT_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) - ++dirWriteCount[j->getBaseDirPf<LEFT_SIDE>()]; + incWriteCount(j->getBaseDirPf<LEFT_SIDE>()); } - if (folderPairStat.getOverwrite() + folderPairStat.getDelete() > 0) + if (folderPairStat.getUpdate() + folderPairStat.getDelete() > 0) { if (folderPairCfg.handleDeletion == zen::MOVE_TO_CUSTOM_DIRECTORY) { @@ -1500,7 +1823,9 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf } //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 && + if (folderPairStat.getCreate() + + folderPairStat.getUpdate() + + folderPairStat.getConflict() == 0 && folderPairStat.getDelete() > 0) //deletions only... (respect filtered items!) { Zstring missingSrcDir; @@ -1522,9 +1847,8 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf significantDiff.push_back(std::make_pair(j->getBaseDirPf<LEFT_SIDE>(), j->getBaseDirPf<RIGHT_SIDE>())); //check for sufficient free diskspace in left directory - const std::pair<zen::Int64, zen::Int64> spaceNeeded = DiskSpaceNeeded(*j, - delHandlerFp.first.deletionFreesSpace(), - delHandlerFp.second.deletionFreesSpace()).getSpaceTotal(); + const std::pair<Int64, Int64> spaceNeeded = DiskSpaceNeeded::calculate(*j, delHandlerFp.first.deletionFreesSpace(), + delHandlerFp.second.deletionFreesSpace()); wxLongLong freeDiskSpaceLeft; if (wxGetDiskSpace(toWx(j->getBaseDirPf<LEFT_SIDE>()), NULL, &freeDiskSpaceLeft)) { @@ -1546,14 +1870,14 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf #ifdef FFS_WIN if (folderPairCfg.handleDeletion == MOVE_TO_RECYCLE_BIN) { - if (folderPairStat.getOverwrite<LEFT_SIDE>() + - folderPairStat.getDelete <LEFT_SIDE>() > 0 && + 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.getOverwrite<RIGHT_SIDE>() + - folderPairStat.getDelete <RIGHT_SIDE>() > 0 && + if (folderPairStat.getUpdate<RIGHT_SIDE>() + + folderPairStat.getDelete<RIGHT_SIDE>() > 0 && recycleBinStatus(j->getBaseDirPf<RIGHT_SIDE>()) != STATUS_REC_EXISTS) recyclMissing.insert(j->getBaseDirPf<RIGHT_SIDE>()); @@ -1565,20 +1889,20 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf if (statisticsTotal.getConflict() > 0) { //show the first few conflicts in warning message also: - wxString warningMessage = wxString(_("Unresolved conflicts existing!")) + - wxT(" (") + toStringSep(statisticsTotal.getConflict()) + wxT(")\n\n"); + std::wstring warningMessage = _("Unresolved conflicts existing!") + + L" (" + toStringSep(statisticsTotal.getConflict()) + L")\n\n"; const SyncStatistics::ConflictTexts& firstConflicts = statisticsTotal.getFirstConflicts(); //get first few sync conflicts for (SyncStatistics::ConflictTexts::const_iterator i = firstConflicts.begin(); i != firstConflicts.end(); ++i) { - wxString conflictDescription = i->second; + std::wstring conflictDescription = i->second; //conflictDescription.Replace(wxT("\n"), wxT(" ")); //remove line-breaks warningMessage += std::wstring(L"\"") + i->first + L"\": " + conflictDescription + L"\n\n"; } if (statisticsTotal.getConflict() > static_cast<int>(firstConflicts.size())) - warningMessage += wxT("[...]\n\n"); + warningMessage += L"[...]\n\n"; warningMessage += _("You can ignore conflicts and continue synchronization."); @@ -1589,13 +1913,14 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf //check if more than 50% of total number of files/dirs are to be created/overwritten/deleted if (!significantDiff.empty()) { - wxString warningMessage = _("Significant difference detected:"); + std::wstring warningMessage = _("Significant difference detected:"); for (DirPairList::const_iterator i = significantDiff.begin(); i != significantDiff.end(); ++i) warningMessage += std::wstring(L"\n\n") + i->first + L" <-> " + L"\n" + i->second; - warningMessage += wxString(L"\n\n") + _("More than 50% of the total number of files will be copied or deleted!"); + warningMessage += L"\n\n"; + warningMessage += _("More than 50% of the total number of files will be copied or deleted!"); procCallback.reportWarning(warningMessage, m_warnings.warningSignificantDifference); } @@ -1604,13 +1929,13 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf //check for sufficient free diskspace if (!diskSpaceMissing.empty()) { - wxString warningMessage = _("Not enough free disk space available in:"); + std::wstring warningMessage = _("Not enough free disk space available in:"); for (auto i = diskSpaceMissing.begin(); i != diskSpaceMissing.end(); ++i) warningMessage += std::wstring(L"\n\n") + L"\"" + i->first + L"\"\n" + - _("Free disk space required:") + wxT(" ") + filesizeToShortString(to<UInt64>(i->second.first)) + L"\n" + - _("Free disk space available:") + wxT(" ") + filesizeToShortString(to<UInt64>(i->second.second)); + _("Free disk space required:") + L" " + filesizeToShortString(to<UInt64>(i->second.first)) + L"\n" + + _("Free disk space available:") + L" " + filesizeToShortString(to<UInt64>(i->second.second)); procCallback.reportWarning(warningMessage, m_warnings.warningNotEnoughDiskSpace); } @@ -1620,11 +1945,11 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf #ifdef FFS_WIN if (!recyclMissing.empty()) { - wxString warningMessage = _("Recycle Bin is not available for the following paths! Files will be deleted permanently instead:"); + std::wstring warningMessage = _("Recycle Bin is not available for the following paths! Files will be deleted permanently instead:"); warningMessage += L"\n"; std::for_each(recyclMissing.begin(), recyclMissing.end(), - [&](const Zstring& path) { warningMessage += L"\n" + toWx(path); }); + [&](const Zstring& path) { warningMessage += L"\n" + utf8CvrtTo<std::wstring>(path); }); procCallback.reportWarning(warningMessage, m_warnings.warningRecyclerMissing); } @@ -1632,15 +1957,18 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf //check if folders are used by multiple pairs in read/write access std::vector<Zstring> conflictDirs; - for (DirWriteMap::const_iterator i = dirWriteCount.begin(); i != dirWriteCount.end(); ++i) - if (i->second >= 2 || //multiple write accesses - (i->second == 1 && dirReadCount.find(i->first) != dirReadCount.end())) //read/write access - conflictDirs.push_back(i->first); + for (auto iter = dirReadWriteCount.cbegin(); iter != dirReadWriteCount.cend(); ++iter) + { + const auto& countRef = iter->second; + if (countRef.second >= 2 || //multiple write accesses + (countRef.second == 1 && countRef.first >= 1)) //read/write access + conflictDirs.push_back(iter->first); + } if (!conflictDirs.empty()) { - wxString warningMessage = wxString(_("A directory will be modified which is part of multiple folder pairs! Please review synchronization settings!")) + wxT("\n"); - for (std::vector<Zstring>::const_iterator i = conflictDirs.begin(); i != conflictDirs.end(); ++i) + std::wstring warningMessage = _("A directory will be modified which is part of multiple folder pairs! Please review synchronization settings!") + L"\n"; + for (auto i = conflictDirs.begin(); i != conflictDirs.end(); ++i) warningMessage += std::wstring(L"\n") + L"\"" + *i + L"\""; procCallback.reportWarning(warningMessage, m_warnings.warningMultiFolderWriteAccess); @@ -1677,8 +2005,8 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf makeSameLength(left, right); const std::wstring statusTxt = _("Processing folder pair:") + L" \n" + - L"\t" + left + L"\"" + j->getBaseDirPf<LEFT_SIDE>() + L"\"" + L" \n" + - L"\t" + right + L"\"" + j->getBaseDirPf<RIGHT_SIDE>() + L"\""; + L" " + left + L"\"" + j->getBaseDirPf<LEFT_SIDE>() + L"\"" + L" \n" + + L" " + right + L"\"" + j->getBaseDirPf<RIGHT_SIDE>() + L"\""; procCallback.reportInfo(statusTxt); //------------------------------------------------------------------------------------------ @@ -1725,7 +2053,6 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf if (folderPairCfg.inAutomaticMode) { procCallback.reportStatus(_("Generating database...")); - procCallback.forceUiRefresh(); tryReportingError(procCallback, [&]() { zen::saveToDisk(*j); }); //throw FileError guardUpdateDb.dismiss(); @@ -1737,7 +2064,7 @@ void SyncProcess::startSynchronizationProcess(const std::vector<FolderPairSyncCf } catch (const std::exception& e) { - procCallback.reportFatalError(wxString::FromUTF8(e.what())); + procCallback.reportFatalError(utf8CvrtTo<std::wstring>(e.what())); } } @@ -1768,7 +2095,7 @@ public: } private: - UInt64& bytesReported_; + UInt64& bytesReported_; ProcessCallback& statusHandler_; DelTargetCommand cmd_; }; @@ -1776,13 +2103,13 @@ private: //copy file while refreshing UI template <SelectedSide side, class DelTargetCommand> -void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const DelTargetCommand& cmd, FileDescriptor& sourceAttr) const +void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const DelTargetCommand& cmd, FileAttrib& newAttr) const { const UInt64 totalBytesToCpy = fileObj.getFileSize<OtherSide<side>::result>(); Zstring source = fileObj.getFullName<OtherSide<side>::result>(); const Zstring& target = fileObj.getBaseDirPf<side>() + fileObj.getRelativeName<OtherSide<side>::result>(); - auto copyOperation = [&]() + auto copyOperation = [&] { //start of (possibly) long-running copy process: ensure status updates are performed regularly UInt64 bytesReported; @@ -1790,16 +2117,13 @@ void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const zen::ScopeGuard guardStatistics = zen::makeGuard([&]() { procCallback_.updateProcessedData(0, -1 * to<Int64>(bytesReported)); }); WhileCopying<DelTargetCommand> 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 - - sourceAttr = FileDescriptor(fileAttr.modificationTime, fileAttr.fileSize); + target, + copyFilePermissions, + transactionalFileCopy, + &callback, + &newAttr); //throw FileError, ErrorFileLocked //inform about the (remaining) processed amount of data procCallback_.updateProcessedData(0, to<Int64>(totalBytesToCpy) - to<Int64>(bytesReported)); @@ -1851,24 +2175,6 @@ void SynchronizeFolderPair::copyFileUpdatingTo(const FileMapping& fileObj, const } -template <zen::SelectedSide side> -void SynchronizeFolderPair::deleteSymlink(const SymLinkMapping& linkObj) const -{ - const DeletionHandling& delHandling = side == LEFT_SIDE ? delHandlingLeft_ : delHandlingRight_; - - switch (linkObj.getLinkType<side>()) - { - case LinkDescriptor::TYPE_DIR: - 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 - break; - } -} - - //--------------------- data verification ------------------------- //callback functionality for status updates while verifying @@ -1936,8 +2242,7 @@ private: void SynchronizeFolderPair::verifyFileCopy(const Zstring& source, const Zstring& target) const { - wxString logText = replaceCpy(txtVerifying, L"%x", utf8CvrtTo<wxString>(target)); - procCallback_.reportInfo(logText); + procCallback_.reportInfo(replaceCpy(txtVerifying, L"%x", utf8CvrtTo<std::wstring>(target))); VerifyStatusUpdater callback(procCallback_); |