From c0fce877c478ddbf71a1b651c789e5ea00a00144 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 17:05:30 +0200 Subject: 3.4 --- synchronization.cpp | 790 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 495 insertions(+), 295 deletions(-) (limited to 'synchronization.cpp') diff --git a/synchronization.cpp b/synchronization.cpp index b00d35b7..9cb9054c 100644 --- a/synchronization.cpp +++ b/synchronization.cpp @@ -1,3 +1,9 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) 2008-2010 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** +// #include "synchronization.h" #include #include @@ -8,6 +14,7 @@ #include "shared/systemConstants.h" #include "library/statusHandler.h" #include "shared/fileHandling.h" +#include "shared/recycler.h" #include #include #include "shared/globalFunctions.h" @@ -18,6 +25,7 @@ #ifdef FFS_WIN #include "shared/shadow.h" #include "shared/longPathPrefix.h" +#include #endif using namespace FreeFileSync; @@ -194,22 +202,30 @@ void SyncStatistics::getDirNumbers(const DirMapping& dirObj) std::vector FreeFileSync::extractSyncCfg(const MainConfiguration& mainCfg) { - std::vector output; + //merge first and additional pairs + std::vector allPairs; + allPairs.push_back(mainCfg.firstPair); + allPairs.insert(allPairs.end(), + mainCfg.additionalPairs.begin(), //add additional pairs + mainCfg.additionalPairs.end()); - //add main pair - output.push_back( - FolderPairSyncCfg(mainCfg.syncConfiguration.automatic, - mainCfg.handleDeletion, - wxToZ(mainCfg.customDeletionDirectory))); + std::vector output; - //add additional pairs - for (std::vector::const_iterator i = mainCfg.additionalPairs.begin(); i != mainCfg.additionalPairs.end(); ++i) + //process all pairs + for (std::vector::const_iterator i = allPairs.begin(); i != allPairs.end(); ++i) output.push_back( - FolderPairSyncCfg(i->altSyncConfig.get() ? i->altSyncConfig->syncConfiguration.automatic : mainCfg.syncConfiguration.automatic, - i->altSyncConfig.get() ? i->altSyncConfig->handleDeletion : mainCfg.handleDeletion, - wxToZ(i->altSyncConfig.get() ? i->altSyncConfig->customDeletionDirectory : mainCfg.customDeletionDirectory))); + i->altSyncConfig.get() ? + + FolderPairSyncCfg(i->altSyncConfig->syncConfiguration.automatic, + i->altSyncConfig->handleDeletion, + wxToZ(i->altSyncConfig->customDeletionDirectory)) : + + FolderPairSyncCfg(mainCfg.syncConfiguration.automatic, + mainCfg.handleDeletion, + wxToZ(mainCfg.customDeletionDirectory))); return output; } +//------------------------------------------------------------------------------------------------------------ template @@ -297,6 +313,7 @@ std::pair freeDiskSpaceNeeded(const BaseDirMapping& base assert(false); return std::make_pair(2000000000, 2000000000); //dummy } +//------------------------------------------------------------------------------------------------------------ bool synchronizationNeeded(const SyncStatistics& statisticsTotal) @@ -336,27 +353,65 @@ bool significantDifferenceDetected(const SyncStatistics& folderPairStat) return changedRows >= 10 && changedRows > 0.5 * folderPairStat.getRowCount(); } +//################################################################################################################# -//################################################################################################################# +FolderPairSyncCfg::FolderPairSyncCfg(bool automaticMode, + const DeletionPolicy handleDel, + const Zstring& custDelDir) : + inAutomaticMode(automaticMode), + handleDeletion(handleDel), + custDelFolder(FreeFileSync::getFormattedDirectoryName(custDelDir)) {} +//----------------------------------------------------------------------------------------------------------- -/*add some postfix to alternate deletion directory: customDir\2009-06-30 12-59-12\ */ -Zstring getSessionDeletionDir(const Zstring& customDeletionDirectory) +template +inline +void tryReportingError(StatusHandler& handler, Function cmd) { - wxString timeNow = wxDateTime::Now().FormatISOTime(); - timeNow.Replace(wxT(":"), wxT("-")); + while (true) + { + try + { + cmd(); + break; + } + catch (FileError& error) + { + //User abort when copying files or moving files/directories into custom deletion directory: + //windows build: abort if requested, don't show error message if cancelled by user! + //linux build: this refresh is not necessary, because user abort triggers an AbortThisProcess() exception without a FileError() + handler.requestUiRefresh(true); //may throw! - const wxString sessionDir = wxDateTime::Now().FormatISODate() + wxChar(' ') + timeNow; + ErrorHandler::Response rv = handler.reportError(error.show()); //may throw! + if ( rv == ErrorHandler::IGNORE_ERROR) + break; + else if (rv == ErrorHandler::RETRY) + ; //continue with loop + else + throw std::logic_error("Programming Error: Unknown return value!"); + } + } +} - Zstring formattedDirectory = FreeFileSync::getFormattedDirectoryName(customDeletionDirectory); + +/* +add some postfix to alternate deletion directory: deletionDirectory\2010-06-30 12-59-12\ +*/ +Zstring getSessionDeletionDir(const Zstring& deletionDirectory, const Zstring& prefix = Zstring()) +{ + Zstring formattedDirectory = deletionDirectory; if (formattedDirectory.empty()) return Zstring(); //no valid directory for deletion specified (checked later) if (!formattedDirectory.EndsWith(globalFunctions::FILE_NAME_SEPARATOR)) formattedDirectory += globalFunctions::FILE_NAME_SEPARATOR; - formattedDirectory += wxToZ(sessionDir); + wxString timeNow = wxDateTime::Now().FormatISOTime(); + timeNow.Replace(wxT(":"), wxT("-")); + + const wxString sessionName = wxDateTime::Now().FormatISODate() + wxChar(' ') + timeNow; + formattedDirectory += prefix + wxToZ(sessionName); //ensure that session directory does not yet exist (must be unique) if (FreeFileSync::dirExists(formattedDirectory)) @@ -386,63 +441,344 @@ SyncProcess::SyncProcess(bool copyFileSymLinks, copyLockedFiles_(copyLockedFiles), m_warnings(warnings), statusUpdater(handler) {} +//-------------------------------------------------------------------------------------------------------------- -struct DeletionHandling +class DeletionHandling { +public: DeletionHandling(const DeletionPolicy handleDel, - const Zstring& custDelFolder) : - handleDeletion(handleDel), - currentDelFolder(getSessionDeletionDir(custDelFolder)), //ends with path separator - txtMoveFileUserDefined( wxToZ(_("Moving file %x to user-defined directory %y")). Replace(DefaultStr("%x"), DefaultStr("\"%x\"\n"), false).Replace(DefaultStr("%y"), Zstring(DefaultStr("\"")) + custDelFolder + DefaultStr("\""), false)), - txtMoveFolderUserDefined(wxToZ(_("Moving folder %x to user-defined directory %y")).Replace(DefaultStr("%x"), DefaultStr("\"%x\"\n"), false).Replace(DefaultStr("%y"), Zstring(DefaultStr("\"")) + custDelFolder + DefaultStr("\""), false)) - {} - - DeletionPolicy handleDeletion; - Zstring currentDelFolder; //alternate deletion folder for current folder pair (with timestamp, ends with path separator) + const Zstring& custDelFolder, + const Zstring& baseDirLeft, + const Zstring& baseDirRight, + StatusHandler& statusUpdater); + ~DeletionHandling(); //always (try to) clean up, even if synchronization is aborted! + + //clean-up temporary directory (recycler bin optimization) + void tryCleanup() const; //throw FileError() -> call this in non-exceptional coding, i.e. after Sync somewhere! + + template + void removeFile(const FileMapping& fileObj) const; //throw FileError() + + template + void removeFolder(const DirMapping& dirObj) const; //throw FileError() + + const Zstring& getTxtRemovingFile() const; //status text templates + const Zstring& getTxtRemovingDir() const; //status text templates + +private: + template + const Zstring& getSessionDir() const; + + void tryCleanupLeft() const; //throw FileError() + void tryCleanupRight() const; //throw FileError() + + const DeletionPolicy deletionType; + StatusHandler& statusUpdater_; + + Zstring sessionDelDirLeft; //target deletion folder for current folder pair (with timestamp, ends with path separator) + Zstring sessionDelDirRight; // + //preloaded status texts: - const Zstring txtMoveFileUserDefined; - const Zstring txtMoveFolderUserDefined; + Zstring txtRemovingFile; + Zstring txtRemovingDirectory; }; -template +DeletionHandling::DeletionHandling(const DeletionPolicy handleDel, + const Zstring& custDelFolder, + const Zstring& baseDirLeft, + const Zstring& baseDirRight, + StatusHandler& statusUpdater) : + deletionType(handleDel), + statusUpdater_(statusUpdater) +{ + switch (handleDel) + { + case DELETE_PERMANENTLY: + txtRemovingFile = wxToZ(_("Deleting file %x")).Replace(DefaultStr("%x"), DefaultStr("\n\"%x\""), false); + txtRemovingDirectory = wxToZ(_("Deleting folder %x")).Replace( DefaultStr("%x"), DefaultStr("\n\"%x\""), false); + break; + + case MOVE_TO_RECYCLE_BIN: + sessionDelDirLeft = getSessionDeletionDir(baseDirLeft, DefaultStr("FFS ")); + sessionDelDirRight = getSessionDeletionDir(baseDirRight, DefaultStr("FFS ")); + + txtRemovingFile = txtRemovingDirectory = wxToZ(_("Moving %x to Recycle Bin")).Replace(DefaultStr("%x"), DefaultStr("\"%x\""), false); + break; + + case MOVE_TO_CUSTOM_DIRECTORY: + sessionDelDirLeft = sessionDelDirRight = getSessionDeletionDir(custDelFolder); + + txtRemovingFile = wxToZ(_("Moving file %x to user-defined directory %y")). Replace(DefaultStr("%x"), DefaultStr("\"%x\"\n"), false).Replace(DefaultStr("%y"), Zstring(DefaultStr("\"")) + custDelFolder + DefaultStr("\""), false); + txtRemovingDirectory = wxToZ(_("Moving folder %x to user-defined directory %y")).Replace(DefaultStr("%x"), DefaultStr("\"%x\"\n"), false).Replace(DefaultStr("%y"), Zstring(DefaultStr("\"")) + custDelFolder + DefaultStr("\""), false); + break; + } +} + + +DeletionHandling::~DeletionHandling() +{ + try //always (try to) clean up, even if synchronization is aborted! + { + tryCleanupLeft(); + } + catch (...) {} + + try //always clean up BOTH sides separately! + { + tryCleanupRight(); + } + catch (...) {} +} + + +void DeletionHandling::tryCleanup() const //throw(AbortThisProcess) +{ + tryReportingError(statusUpdater_, boost::bind(&DeletionHandling::tryCleanupLeft, this)); + tryReportingError(statusUpdater_, boost::bind(&DeletionHandling::tryCleanupRight, this)); +} + + +void DeletionHandling::tryCleanupLeft() const //throw FileError() +{ + if (deletionType == MOVE_TO_RECYCLE_BIN) //clean-up temporary directory (recycle bin) + FreeFileSync::moveToRecycleBin(sessionDelDirLeft.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR)); //throw (FileError) +} + + +void DeletionHandling::tryCleanupRight() const //throw FileError() +{ + if (deletionType == MOVE_TO_RECYCLE_BIN) //clean-up temporary directory (recycle bin) + FreeFileSync::moveToRecycleBin(sessionDelDirRight.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR)); //throw (FileError) +} + + inline -void tryReportingError(StatusHandler& handler, Function cmd) +const Zstring& DeletionHandling::getTxtRemovingFile() const { - while (true) + return txtRemovingFile; +} + + +inline +const Zstring& DeletionHandling::getTxtRemovingDir() const +{ + return txtRemovingDirectory; +} + + +template <> +inline +const Zstring& DeletionHandling::getSessionDir() const +{ + return sessionDelDirLeft; +} + +template <> +inline +const Zstring& DeletionHandling::getSessionDir() const +{ + return sessionDelDirRight; +} + + +class MoveFileCallbackImpl : public MoveFileCallback //callback functionality +{ +public: + MoveFileCallbackImpl(StatusHandler& handler) : statusHandler_(handler) {} + + virtual Response requestUiRefresh() //DON'T throw exceptions here, at least in Windows build! { - try +#ifdef FFS_WIN + statusHandler_.requestUiRefresh(false); //don't allow throwing exception within this call: windows copying callback can't handle this + if (statusHandler_.abortIsRequested()) + return MoveFileCallback::CANCEL; +#elif defined FFS_LINUX + statusHandler_.requestUiRefresh(); //exceptions may be thrown here! +#endif + return MoveFileCallback::CONTINUE; + } + +private: + StatusHandler& statusHandler_; +}; + + +template +void DeletionHandling::removeFile(const FileMapping& fileObj) const +{ + switch (deletionType) + { + case FreeFileSync::DELETE_PERMANENTLY: + FreeFileSync::removeFile(fileObj.getFullName()); + break; + + case FreeFileSync::MOVE_TO_RECYCLE_BIN: + if (FreeFileSync::fileExists(fileObj.getFullName())) { - cmd(); - break; + const Zstring targetFile = getSessionDir() + fileObj.getRelativeName(); //altDeletionDir ends with path separator + const Zstring targetDir = targetFile.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); + + if (!FreeFileSync::dirExists(targetDir)) + { + if (!targetDir.empty()) //kind of pathological ? + //lazy creation of alternate deletion directory (including super-directories of targetFile) + FreeFileSync::createDirectory(targetDir, Zstring(), false); + } + + try //rename file: no copying!!! + { + //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! + FreeFileSync::renameFile(fileObj.getFullName(), targetFile); //throw (FileError); + } + catch (...) + { + //if anything went wrong, move to recycle bin the standard way (single file processing: slow) + FreeFileSync::moveToRecycleBin(fileObj.getFullName()); //throw (FileError) + } } - catch (FileError& error) + break; + + case FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY: + if (FreeFileSync::fileExists(fileObj.getFullName())) { - //User abort when copying files or moving files/directories into custom deletion directory: - //windows build: abort if requested, don't show error message if cancelled by user! - //linux build: this refresh is not necessary, because user abort triggers an AbortThisProcess() exception without a FileError() - handler.requestUiRefresh(true); //may throw! + const Zstring targetFile = getSessionDir() + fileObj.getRelativeName(); //altDeletionDir ends with path separator + const Zstring targetDir = targetFile.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - ErrorHandler::Response rv = handler.reportError(error.show()); //may throw! - if ( rv == ErrorHandler::IGNORE_ERROR) - break; - else if (rv == ErrorHandler::RETRY) - ; //continue with loop - else - throw std::logic_error("Programming Error: Unknown return value!"); + if (!FreeFileSync::dirExists(targetDir)) + { + if (!targetDir.empty()) //kind of pathological ? + //lazy creation of alternate deletion directory (including super-directories of targetFile) + FreeFileSync::createDirectory(targetDir, Zstring(), false); + /*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 */ + } + + MoveFileCallbackImpl callBack(statusUpdater_); //if file needs to be copied we need callback functionality to update screen and offer abort + FreeFileSync::moveFile(fileObj.getFullName(), targetFile, &callBack); + } + break; + } +} + + +template +void DeletionHandling::removeFolder(const DirMapping& dirObj) const +{ + switch (deletionType) + { + case FreeFileSync::DELETE_PERMANENTLY: + FreeFileSync::removeDirectory(dirObj.getFullName()); + break; + + case FreeFileSync::MOVE_TO_RECYCLE_BIN: + if (FreeFileSync::dirExists(dirObj.getFullName())) + { + const Zstring targetDir = getSessionDir() + dirObj.getRelativeName(); + const Zstring targetSuperDir = targetDir.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); + + if (!FreeFileSync::dirExists(targetSuperDir)) + { + if (!targetSuperDir.empty()) //kind of pathological ? + //lazy creation of alternate deletion directory (including super-directories of targetFile) + FreeFileSync::createDirectory(targetSuperDir, Zstring(), false); + } + + try //rename directory: no copying!!! + { + //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! + FreeFileSync::renameFile(dirObj.getFullName(), targetDir); //throw (FileError); + } + catch (...) + { + //if anything went wrong, move to recycle bin the standard way (single file processing: slow) + FreeFileSync::moveToRecycleBin(dirObj.getFullName()); //throw (FileError) + + } + } + break; + + case FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY: + if (FreeFileSync::dirExists(dirObj.getFullName())) + { + const Zstring targetDir = getSessionDir() + dirObj.getRelativeName(); + const Zstring targetSuperDir = targetDir.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); + + if (!FreeFileSync::dirExists(targetSuperDir)) + { + if (!targetSuperDir.empty()) //kind of pathological ? + //lazy creation of alternate deletion directory (including super-directories of targetFile) + FreeFileSync::createDirectory(targetSuperDir, Zstring(), false); + /*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 */ + } + + MoveFileCallbackImpl callBack(statusUpdater_); //if files need to be copied, we need callback functionality to update screen and offer abort + FreeFileSync::moveDirectory(dirObj.getFullName(), targetDir, true, &callBack); } + break; } } +//---------------------------------------------------------------------------------------- + +//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() > fileObj.getFileSize(); + case SO_OVERWRITE_RIGHT: + return fileObj.getFileSize() < fileObj.getFileSize(); + + case SO_CREATE_NEW_LEFT: + case SO_CREATE_NEW_RIGHT: + case SO_DO_NOTHING: + case SO_EQUAL: + case SO_UNRESOLVED_CONFLICT: + return false; + } + return false; //dummy +} inline -bool deletionImminent(const FileSystemObject& fsObj) +bool diskSpaceIsReduced(const DirMapping& dirObj) { - //test if current sync-line will result in deletion of files -> used to avoid disc space bottlenecks - const SyncOperation op = fsObj.getSyncOperation(); - return op == FreeFileSync::SO_DELETE_LEFT || op == FreeFileSync::SO_DELETE_RIGHT; + 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: + case SO_UNRESOLVED_CONFLICT: + assert(false); + case SO_CREATE_NEW_LEFT: + case SO_CREATE_NEW_RIGHT: + case SO_DO_NOTHING: + case SO_EQUAL: + return false; + } + return false; //dummy } +//---------------------------------------------------------------------------------------- class RemoveInvalid @@ -461,14 +797,14 @@ private: }; -class FreeFileSync::SyncRecursively +class FreeFileSync::SynchronizeFolderPair { public: - SyncRecursively(const SyncProcess& syncProc, + SynchronizeFolderPair(const SyncProcess& syncProc, #ifdef FFS_WIN - ShadowCopy* shadowCopyHandler, + ShadowCopy* shadowCopyHandler, #endif - const DeletionHandling& delHandling) : + const DeletionHandling& delHandling) : statusUpdater_(syncProc.statusUpdater), #ifdef FFS_WIN shadowCopyHandler_(shadowCopyHandler), @@ -480,66 +816,22 @@ public: txtCopyingFile(wxToZ(_("Copying file %x to %y")).Replace(DefaultStr("%x"), DefaultStr("\"%x\""), false).Replace(DefaultStr("%y"), DefaultStr("\n\"%y\""), false)), txtOverwritingFile(wxToZ(_("Copying file %x to %y overwriting target")).Replace(DefaultStr("%x"), DefaultStr("\"%x\""), false).Replace(DefaultStr("%y"), DefaultStr("\n\"%y\""), false)), txtCreatingFolder(wxToZ(_("Creating folder %x")).Replace(DefaultStr("%x"), DefaultStr("\n\"%x\""), false)), - txtDeletingFile(wxToZ(_("Deleting file %x")).Replace(DefaultStr("%x"), DefaultStr("\n\"%x\""), false)), - txtDeletingFolder(wxToZ(_("Deleting folder %x")).Replace( DefaultStr("%x"), DefaultStr("\n\"%x\""), false)), - txtMoveToRecycler(wxToZ(_("Moving %x to Recycle Bin")).Replace(DefaultStr("%x"), DefaultStr("\"%x\""), false)), txtVerifying(wxToZ(_("Verifying file %x")).Replace(DefaultStr("%x"), DefaultStr("\n\"%x\""), false)) {} - template //"true" if files deletion shall happen only - void execute(HierarchyObject& hierObj) - { - //synchronize files: - for (HierarchyObject::SubFileMapping::iterator i = hierObj.subFiles.begin(); i != hierObj.subFiles.end(); ++i) - { - if ( ( deleteOnly && deletionImminent(*i)) || - (!deleteOnly && !deletionImminent(*i))) - tryReportingError(statusUpdater_, boost::bind(&SyncRecursively::synchronizeFile, this, boost::ref(*i))); - } - - //synchronize folders: - for (HierarchyObject::SubDirMapping::iterator i = hierObj.subDirs.begin(); i != hierObj.subDirs.end(); ++i) - { - const SyncOperation syncOp = i->getSyncOperation(); - if ( ( deleteOnly && deletionImminent(*i)) || //ensure folder creation happens in second pass, to enable time adaption below - (!deleteOnly && !deletionImminent(*i))) // - tryReportingError(statusUpdater_, boost::bind(&SyncRecursively::synchronizeFolder, this, boost::ref(*i))); - - //recursive synchronization: - execute(*i); - - //adapt folder modification dates: apply AFTER all subobjects have been synced to preserve folder modification date! - switch (syncOp) - { - case SO_CREATE_NEW_LEFT: - copyFileTimes(i->getFullName(), i->getFullName()); //throw() - break; - case SO_CREATE_NEW_RIGHT: - copyFileTimes(i->getFullName(), i->getFullName()); //throw() - break; - case SO_OVERWRITE_RIGHT: - case SO_OVERWRITE_LEFT: - case SO_UNRESOLVED_CONFLICT: - assert(false); - case SO_DELETE_LEFT: - case SO_DELETE_RIGHT: - case SO_DO_NOTHING: - case SO_EQUAL: - break; - } - } + template //"true" if files deletion shall happen only + void startSync(BaseDirMapping& baseMap) + { + execute(baseMap); } private: + template //"true" if files deletion shall happen only + void execute(HierarchyObject& hierObj); + void synchronizeFile(FileMapping& fileObj) const; void synchronizeFolder(DirMapping& dirObj) const; - template - void removeFile(const FileMapping& fileObj, bool showStatusUpdate) const; - - template - void removeFolder(const DirMapping& dirObj) const; - void copyFileUpdating(const Zstring& source, const Zstring& target, const wxULongLong& sourceFileSize) const; void verifyFileCopy(const Zstring& source, const Zstring& target) const; @@ -559,97 +851,57 @@ private: const Zstring txtCopyingFile; const Zstring txtOverwritingFile; const Zstring txtCreatingFolder; - const Zstring txtDeletingFile; - const Zstring txtDeletingFolder; - const Zstring txtMoveToRecycler; const Zstring txtVerifying; }; -class MoveFileCallbackImpl : public MoveFileCallback //callback functionality +template //"true" if files deletion shall happen only +void SynchronizeFolderPair::execute(HierarchyObject& hierObj) { -public: - MoveFileCallbackImpl(StatusHandler& handler) : statusHandler_(handler) {} - - virtual Response requestUiRefresh() //DON'T throw exceptions here, at least in Windows build! + //synchronize files: + for (HierarchyObject::SubFileMapping::iterator i = hierObj.subFiles.begin(); i != hierObj.subFiles.end(); ++i) { -#ifdef FFS_WIN - statusHandler_.requestUiRefresh(false); //don't allow throwing exception within this call: windows copying callback can't handle this - if (statusHandler_.abortIsRequested()) - return MoveFileCallback::CANCEL; -#elif defined FFS_LINUX - statusHandler_.requestUiRefresh(); //exceptions may be thrown here! -#endif - return MoveFileCallback::CONTINUE; + if ( ( reduceDiskSpace && diskSpaceIsReduced(*i)) || + (!reduceDiskSpace && !diskSpaceIsReduced(*i))) + tryReportingError(statusUpdater_, boost::bind(&SynchronizeFolderPair::synchronizeFile, this, boost::ref(*i))); } -private: - StatusHandler& statusHandler_; -}; + //synchronize folders: + for (HierarchyObject::SubDirMapping::iterator i = hierObj.subDirs.begin(); i != hierObj.subDirs.end(); ++i) + { + const SyncOperation syncOp = i->getSyncOperation(); + if ( ( reduceDiskSpace && diskSpaceIsReduced(*i)) || //ensure folder creation happens in second pass, to enable time adaption below + (!reduceDiskSpace && !diskSpaceIsReduced(*i))) // + tryReportingError(statusUpdater_, boost::bind(&SynchronizeFolderPair::synchronizeFolder, this, boost::ref(*i))); -template -inline -void SyncRecursively::removeFile(const FileMapping& fileObj, bool showStatusUpdate) const -{ - Zstring statusText; + //recursive synchronization: + execute(*i); - switch (delHandling_.handleDeletion) - { - case FreeFileSync::DELETE_PERMANENTLY: - if (showStatusUpdate) //status information + //adapt folder modification dates: apply AFTER all subobjects have been synced to preserve folder modification date! + switch (syncOp) { - statusText = txtDeletingFile; - statusText.Replace(DefaultStr("%x"), fileObj.getFullName(), false); - statusUpdater_.updateStatusText(statusText); - statusUpdater_.requestUiRefresh(); //trigger display refresh - } - FreeFileSync::removeFile(fileObj.getFullName(), false); - break; - case FreeFileSync::MOVE_TO_RECYCLE_BIN: - if (showStatusUpdate) //status information - { - statusText = txtMoveToRecycler; - statusText.Replace(DefaultStr("%x"), fileObj.getFullName(), false); - statusUpdater_.updateStatusText(statusText); - statusUpdater_.requestUiRefresh(); //trigger display refresh - } - FreeFileSync::removeFile(fileObj.getFullName(), true); - break; - case FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY: - if (FreeFileSync::fileExists(fileObj.getFullName())) - { - if (showStatusUpdate) //status information - { - statusText = delHandling_.txtMoveFileUserDefined; - statusText.Replace(DefaultStr("%x"), fileObj.getFullName(), false); - statusUpdater_.updateStatusText(statusText); - statusUpdater_.requestUiRefresh(); //trigger display refresh - } - const Zstring targetFile = delHandling_.currentDelFolder + fileObj.getRelativeName(); //altDeletionDir ends with path separator - const Zstring targetDir = targetFile.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - - if (!FreeFileSync::dirExists(targetDir)) - { - if (!targetDir.empty()) //kind of pathological ? - //lazy creation of alternate deletion directory (including super-directories of targetFile) - FreeFileSync::createDirectory(targetDir, Zstring(), false); - /*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 */ - } - - MoveFileCallbackImpl callBack(statusUpdater_); //if file needs to be copied we need callback functionality to update screen and offer abort - FreeFileSync::moveFile(fileObj.getFullName(), targetFile, &callBack); + case SO_CREATE_NEW_LEFT: + copyFileTimes(i->getFullName(), i->getFullName()); //throw() + break; + case SO_CREATE_NEW_RIGHT: + copyFileTimes(i->getFullName(), i->getFullName()); //throw() + break; + case SO_OVERWRITE_RIGHT: + case SO_OVERWRITE_LEFT: + case SO_UNRESOLVED_CONFLICT: + assert(false); + case SO_DELETE_LEFT: + case SO_DELETE_RIGHT: + case SO_DO_NOTHING: + case SO_EQUAL: + break; } - break; } } -void SyncRecursively::synchronizeFile(FileMapping& fileObj) const +void SynchronizeFolderPair::synchronizeFile(FileMapping& fileObj) const { Zstring statusText; Zstring target; @@ -681,11 +933,21 @@ void SyncRecursively::synchronizeFile(FileMapping& fileObj) const break; case SO_DELETE_LEFT: - removeFile(fileObj, true); //status updates in subroutine + statusText = delHandling_.getTxtRemovingFile(); + statusText.Replace(DefaultStr("%x"), fileObj.getFullName(), false); + statusUpdater_.updateStatusText(statusText); + statusUpdater_.requestUiRefresh(); //trigger display refresh + + delHandling_.removeFile(fileObj); //throw FileError() break; case SO_DELETE_RIGHT: - removeFile(fileObj, true); //status updates in subroutine + statusText = delHandling_.getTxtRemovingFile(); + statusText.Replace(DefaultStr("%x"), fileObj.getFullName(), false); + statusUpdater_.updateStatusText(statusText); + statusUpdater_.requestUiRefresh(); //trigger display refresh + + delHandling_.removeFile(fileObj); //throw FileError() break; case SO_OVERWRITE_RIGHT: @@ -697,7 +959,7 @@ void SyncRecursively::synchronizeFile(FileMapping& fileObj) const statusUpdater_.updateStatusText(statusText); statusUpdater_.requestUiRefresh(); //trigger display refresh - removeFile(fileObj, false); + delHandling_.removeFile(fileObj); //throw FileError() fileObj.removeObject(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) copyFileUpdating(fileObj.getFullName(), target, fileObj.getFileSize()); @@ -712,7 +974,7 @@ void SyncRecursively::synchronizeFile(FileMapping& fileObj) const statusUpdater_.updateStatusText(statusText); statusUpdater_.requestUiRefresh(); //trigger display refresh - removeFile(fileObj, false); + delHandling_.removeFile(fileObj); //throw FileError() fileObj.removeObject(); //remove file from FileMapping, to keep in sync (if subsequent copying fails!!) copyFileUpdating(fileObj.getFullName(), target, fileObj.getFileSize()); @@ -799,65 +1061,7 @@ void SyncRecursively::synchronizeFile(FileMapping& fileObj) const //} -template -inline -void SyncRecursively::removeFolder(const DirMapping& dirObj) const -{ - Zstring statusText; - - switch (delHandling_.handleDeletion) - { - case FreeFileSync::DELETE_PERMANENTLY: - //status information - statusText = txtDeletingFolder; - statusText.Replace(DefaultStr("%x"), dirObj.getFullName(), false); - statusUpdater_.updateStatusText(statusText); - statusUpdater_.requestUiRefresh(); //trigger display refresh - - FreeFileSync::removeDirectory(dirObj.getFullName(), false); - break; - case FreeFileSync::MOVE_TO_RECYCLE_BIN: - //status information - statusText = txtMoveToRecycler; - statusText.Replace(DefaultStr("%x"), dirObj.getFullName(), false); - statusUpdater_.updateStatusText(statusText); - statusUpdater_.requestUiRefresh(); //trigger display refresh - - FreeFileSync::removeDirectory(dirObj.getFullName(), true); - break; - case FreeFileSync::MOVE_TO_CUSTOM_DIRECTORY: - if (FreeFileSync::dirExists(dirObj.getFullName())) - { - //status information - statusText = delHandling_.txtMoveFolderUserDefined; - statusText.Replace(DefaultStr("%x"), dirObj.getFullName(), false); - statusUpdater_.updateStatusText(statusText); - statusUpdater_.requestUiRefresh(); //trigger display refresh - - const Zstring targetDir = delHandling_.currentDelFolder + dirObj.getRelativeName(); - const Zstring targetSuperDir = targetDir.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR);; - - if (!FreeFileSync::dirExists(targetSuperDir)) - { - if (!targetSuperDir.empty()) //kind of pathological ? - //lazy creation of alternate deletion directory (including super-directories of targetFile) - FreeFileSync::createDirectory(targetSuperDir, Zstring(), false); - /*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 */ - } - - MoveFileCallbackImpl callBack(statusUpdater_); //if files need to be copied, we need callback functionality to update screen and offer abort - FreeFileSync::moveDirectory(dirObj.getFullName(), targetDir, true, &callBack); - } - break; - } -} - - -void SyncRecursively::synchronizeFolder(DirMapping& dirObj) const +void SynchronizeFolderPair::synchronizeFolder(DirMapping& dirObj) const { Zstring statusText; Zstring target; @@ -894,7 +1098,13 @@ void SyncRecursively::synchronizeFolder(DirMapping& dirObj) const break; case SO_DELETE_LEFT: - removeFolder(dirObj); + //status information + statusText = delHandling_.getTxtRemovingDir(); + statusText.Replace(DefaultStr("%x"), dirObj.getFullName(), false); + statusUpdater_.updateStatusText(statusText); + statusUpdater_.requestUiRefresh(); //trigger display refresh + + delHandling_.removeFolder(dirObj); //throw FileError() { //progress indicator update: DON'T forget to notify about implicitly deleted objects! const SyncStatistics subObjects(dirObj); @@ -906,7 +1116,13 @@ void SyncRecursively::synchronizeFolder(DirMapping& dirObj) const break; case SO_DELETE_RIGHT: - removeFolder(dirObj); + //status information + statusText = delHandling_.getTxtRemovingDir(); + statusText.Replace(DefaultStr("%x"), dirObj.getFullName(), false); + statusUpdater_.updateStatusText(statusText); + statusUpdater_.requestUiRefresh(); //trigger display refresh + + delHandling_.removeFolder(dirObj); //throw FileError() { //progress indicator update: DON'T forget to notify about implicitly deleted objects! const SyncStatistics subObjects(dirObj); @@ -935,27 +1151,6 @@ void SyncRecursively::synchronizeFolder(DirMapping& dirObj) const } -class UpdateDatabase -{ -public: - UpdateDatabase(const BaseDirMapping& baseMap, StatusHandler& statusHandler) : - baseMap_(baseMap), - statusHandler_(statusHandler) {} - - //update sync database after synchronization is finished - void updateNow() - { - //these calls may throw in error-callbacks! - tryReportingError(statusHandler_, boost::bind(saveToDisk, boost::cref(baseMap_))); - }; - //_("You can ignore the error to skip current folder pair.")); - -private: - const BaseDirMapping& baseMap_; - StatusHandler& statusHandler_; -}; - - //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) { @@ -1007,14 +1202,14 @@ void SyncProcess::startSynchronizationProcess(const std::vectorfirst) + wxT("\": \t") + i->second + wxT("\n"); + { + wxString conflictDescription = i->second; + conflictDescription.Replace(wxT("\n"), wxT(" ")); //remove line-breaks + + warningMessage += wxString(wxT("\"")) + zToWx(i->first) + wxT("\": ") + conflictDescription + wxT("\n"); + } if (statisticsTotal.getConflict() > static_cast(firstConflicts.size())) warningMessage += wxT("[...]\n"); @@ -1096,7 +1296,7 @@ void SyncProcess::startSynchronizationProcess(const std::vector shadowCopyHandler(copyLockedFiles_ ? new ShadowCopy : NULL); + boost::scoped_ptr shadowCopyHandler(copyLockedFiles_ ? new ShadowCopy : NULL); #endif try @@ -1107,6 +1307,10 @@ void SyncProcess::startSynchronizationProcess(const std::vectorgetBaseDir() == j->getBaseDir()) + continue; + //------------------------------------------------------------------------------------------ //info about folder pair to be processed (useful for logfile) wxString left = wxString(_("Left")) + wxT(": "); @@ -1119,11 +1323,11 @@ void SyncProcess::startSynchronizationProcess(const std::vectorgetBaseDir(), j->getBaseDir(), + statusUpdater); - //exclude some pathological case (leftdir, rightdir are empty) - if (j->getBaseDir() == j->getBaseDir()) - continue; //------------------------------------------------------------------------------------------ //execute synchronization recursively @@ -1134,26 +1338,28 @@ void SyncProcess::startSynchronizationProcess(const std::vector(*j); - SyncRecursively(*this, + SynchronizeFolderPair syncFP( *this, #ifdef FFS_WIN - shadowCopyHandler.get(), + shadowCopyHandler.get(), #endif - currentDelHandling).execute(*j); + currentDelHandling); + + //loop through all files twice; reason: first delete files (or overwrite big ones with smaller ones), then copy rest + syncFP.startSync(*j); + syncFP.startSync(*j); + + + //(try to gracefully) cleanup temporary folders (Recycle bin optimization) -> will be done in DeletionHandling anyway... + currentDelHandling.tryCleanup(); + //------------------------------------------------------------------------------------------ //update synchronization database (automatic sync only) if (folderPairCfg.inAutomaticMode) { - UpdateDatabase syncDB(*j, statusUpdater); statusUpdater.updateStatusText(wxToZ(_("Generating database..."))); statusUpdater.forceUiRefresh(); - syncDB.updateNow(); + tryReportingError(statusUpdater, boost::bind(FreeFileSync::saveToDisk, boost::cref(*j))); //these call may throw in error-callback! } } } @@ -1202,7 +1408,7 @@ private: //copy file while executing statusUpdater->requestUiRefresh() calls -void SyncRecursively::copyFileUpdating(const Zstring& source, const Zstring& target, const wxULongLong& totalBytesToCpy) const +void SynchronizeFolderPair::copyFileUpdating(const Zstring& source, const Zstring& target, const wxULongLong& totalBytesToCpy) const { //create folders first (see http://sourceforge.net/tracker/index.php?func=detail&aid=2628943&group_id=234430&atid=1093080) const Zstring targetDir = target.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); @@ -1319,7 +1525,7 @@ private: }; -void SyncRecursively::verifyFileCopy(const Zstring& source, const Zstring& target) const +void SynchronizeFolderPair::verifyFileCopy(const Zstring& source, const Zstring& target) const { Zstring statusText = txtVerifying; statusText.Replace(DefaultStr("%x"), target, false); @@ -1344,9 +1550,3 @@ void SyncRecursively::verifyFileCopy(const Zstring& source, const Zstring& targe } } - - - - - - -- cgit