diff options
Diffstat (limited to 'synchronization.cpp')
-rw-r--r-- | synchronization.cpp | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/synchronization.cpp b/synchronization.cpp new file mode 100644 index 00000000..9d9fd4d2 --- /dev/null +++ b/synchronization.cpp @@ -0,0 +1,563 @@ +#include "synchronization.h" +#include <wx/intl.h> +#include <wx/msgdlg.h> +#include <wx/log.h> +#include "library/multithreading.h" + +#ifdef FFS_LINUX +#include <utime.h> +#endif // FFS_LINUX + +using namespace FreeFileSync; + + +SyncProcess::SyncProcess(bool useRecycler, bool lineBreakOnMessages, StatusHandler* handler) : + useRecycleBin(useRecycler), + statusUpdater(handler) +{ + if (lineBreakOnMessages) + optionalLineBreak = wxT("\n"); +} + + +inline +SyncConfiguration::Direction getSyncDirection(const CompareFilesResult cmpResult, const SyncConfiguration& config) +{ + switch (cmpResult) + { + case FILE_LEFT_SIDE_ONLY: + return config.exLeftSideOnly; + break; + + case FILE_RIGHT_SIDE_ONLY: + return config.exRightSideOnly; + break; + + case FILE_RIGHT_NEWER: + return config.rightNewer; + break; + + case FILE_LEFT_NEWER: + return config.leftNewer; + break; + + case FILE_DIFFERENT: + return config.different; + break; + + default: + assert (false); + } + return SyncConfiguration::SYNC_DIR_NONE; +} + + +bool getBytesToTransfer(int& objectsToCreate, + int& objectsToOverwrite, + int& objectsToDelete, + double& dataToProcess, + const FileCompareLine& fileCmpLine, + const SyncConfiguration& config) +{ //false if nothing has to be done + + objectsToCreate = 0; //always initialize variables + objectsToOverwrite = 0; + objectsToDelete = 0; + dataToProcess = 0; + + //do not add filtered entries + if (!fileCmpLine.selectedForSynchronization) + return false; + + + switch (fileCmpLine.cmpResult) + { + case FILE_LEFT_SIDE_ONLY: + //get data to process + switch (getSyncDirection(fileCmpLine.cmpResult, config)) + { + case SyncConfiguration::SYNC_DIR_LEFT: //delete file on left + dataToProcess = 0; + objectsToDelete = 1; + break; + case SyncConfiguration::SYNC_DIR_RIGHT: //copy from left to right + dataToProcess = fileCmpLine.fileDescrLeft.fileSize.ToDouble(); + objectsToCreate = 1; + break; + case SyncConfiguration::SYNC_DIR_NONE: + return false; + } + break; + + case FILE_RIGHT_SIDE_ONLY: + switch (getSyncDirection(fileCmpLine.cmpResult, config)) + { + case SyncConfiguration::SYNC_DIR_LEFT: //copy from right to left + dataToProcess = fileCmpLine.fileDescrRight.fileSize.ToDouble();; + objectsToCreate = 1; + break; + case SyncConfiguration::SYNC_DIR_RIGHT: //delete file on right + dataToProcess = 0; + objectsToDelete = 1; + break; + case SyncConfiguration::SYNC_DIR_NONE: + return false; + } + break; + + case FILE_LEFT_NEWER: + case FILE_RIGHT_NEWER: + case FILE_DIFFERENT: + //get data to process + switch (getSyncDirection(fileCmpLine.cmpResult, config)) + { + case SyncConfiguration::SYNC_DIR_LEFT: //copy from right to left + dataToProcess = fileCmpLine.fileDescrRight.fileSize.ToDouble(); + objectsToOverwrite = 1; + break; + case SyncConfiguration::SYNC_DIR_RIGHT: //copy from left to right + dataToProcess = fileCmpLine.fileDescrLeft.fileSize.ToDouble(); + objectsToOverwrite = 1; + break; + case SyncConfiguration::SYNC_DIR_NONE: + return false; + } + break; + + case FILE_EQUAL: + return false; + default: + assert(false); + return false; + }; + + return true; +} + +void copyfileMultithreaded(const wxString& source, const wxString& target, StatusHandler* updateClass); + + +bool SyncProcess::synchronizeFile(const FileCompareLine& cmpLine, const SyncConfiguration& config) +{ //false if nothing was to be done + assert (statusUpdater); + + if (!cmpLine.selectedForSynchronization) return false; + + wxString target; + + //synchronize file: + switch (cmpLine.cmpResult) + { + case FILE_LEFT_SIDE_ONLY: + switch (config.exLeftSideOnly) + { + case SyncConfiguration::SYNC_DIR_LEFT: //delete files on left + statusUpdater->updateStatusText(wxString(_("Deleting file ")) + optionalLineBreak + wxT("\"") + cmpLine.fileDescrLeft.fullName + wxT("\"")); + removeFile(cmpLine.fileDescrLeft.fullName, useRecycleBin); + break; + case SyncConfiguration::SYNC_DIR_RIGHT: //copy files to right + target = cmpLine.fileDescrRight.directory + cmpLine.fileDescrLeft.relativeName; + statusUpdater->updateStatusText(wxString(_("Copying file ")) + optionalLineBreak + wxT("\"") + cmpLine.fileDescrLeft.fullName + wxT("\"") + + _(" to ") + optionalLineBreak + wxT("\"") + target + wxT("\"")); + + copyfileMultithreaded(cmpLine.fileDescrLeft.fullName, target, statusUpdater); + break; + case SyncConfiguration::SYNC_DIR_NONE: + return false; + default: + assert (false); + } + break; + + case FILE_RIGHT_SIDE_ONLY: + switch (config.exRightSideOnly) + { + case SyncConfiguration::SYNC_DIR_LEFT: //copy files to left + target = cmpLine.fileDescrLeft.directory + cmpLine.fileDescrRight.relativeName; + statusUpdater->updateStatusText(wxString(_("Copying file ")) + optionalLineBreak + wxT("\"") + cmpLine.fileDescrRight.fullName + wxT("\"") + + _(" to ") + optionalLineBreak + wxT("\"") + target + wxT("\"")); + + copyfileMultithreaded(cmpLine.fileDescrRight.fullName, target, statusUpdater); + break; + case SyncConfiguration::SYNC_DIR_RIGHT: //delete files on right + statusUpdater->updateStatusText(wxString(_("Deleting file ")) + optionalLineBreak + wxT("\"") + cmpLine.fileDescrRight.fullName + wxT("\"")); + removeFile(cmpLine.fileDescrRight.fullName, useRecycleBin); + break; + case SyncConfiguration::SYNC_DIR_NONE: + return false; + default: + assert (false); + } + break; + + case FILE_LEFT_NEWER: + case FILE_RIGHT_NEWER: + case FILE_DIFFERENT: + switch (getSyncDirection(cmpLine.cmpResult, config)) + { + case SyncConfiguration::SYNC_DIR_LEFT: //copy from right to left + statusUpdater->updateStatusText(wxString(_("Copying file ")) + optionalLineBreak + wxT("\"") + cmpLine.fileDescrRight.fullName + wxT("\"") + + _(" overwriting ") + optionalLineBreak + wxT("\"") + cmpLine.fileDescrLeft.fullName + wxT("\"")); + + removeFile(cmpLine.fileDescrLeft.fullName, useRecycleBin); //only used if switch activated by user, else file is simply deleted + copyfileMultithreaded(cmpLine.fileDescrRight.fullName, cmpLine.fileDescrLeft.fullName, statusUpdater); + break; + case SyncConfiguration::SYNC_DIR_RIGHT: //copy from left to right + statusUpdater->updateStatusText(wxString(_("Copying file ")) + optionalLineBreak + wxT("\"") + cmpLine.fileDescrLeft.fullName + wxT("\"") + + _(" overwriting ") + optionalLineBreak + wxT("\"") + cmpLine.fileDescrRight.fullName + wxT("\"")); + + removeFile(cmpLine.fileDescrRight.fullName, useRecycleBin); //only used if switch activated by user, else file is simply deleted + copyfileMultithreaded(cmpLine.fileDescrLeft.fullName, cmpLine.fileDescrRight.fullName, statusUpdater); + break; + case SyncConfiguration::SYNC_DIR_NONE: + return false; + default: + assert (false); + } + break; + + case FILE_EQUAL: + return false; + + default: + assert (false); + } + return true; +} + + +bool SyncProcess::synchronizeFolder(const FileCompareLine& cmpLine, const SyncConfiguration& config) +{ //false if nothing was to be done + assert (statusUpdater); + + if (!cmpLine.selectedForSynchronization) return false; + + wxString target; + + //synchronize folders: + switch (cmpLine.cmpResult) + { + case FILE_LEFT_SIDE_ONLY: + switch (config.exLeftSideOnly) + { + case SyncConfiguration::SYNC_DIR_LEFT: //delete folders on left + statusUpdater->updateStatusText(wxString(_("Deleting folder ")) + optionalLineBreak + wxT("\"") + cmpLine.fileDescrLeft.fullName + wxT("\"")); + removeDirectory(cmpLine.fileDescrLeft.fullName, useRecycleBin); + break; + case SyncConfiguration::SYNC_DIR_RIGHT: //create folders on right + target = cmpLine.fileDescrRight.directory + cmpLine.fileDescrLeft.relativeName; + statusUpdater->updateStatusText(wxString(_("Creating folder ")) + optionalLineBreak + wxT("\"") + target + wxT("\"")); + + //some check to catch the error that directory on source has been deleted externally after the "compare"... + if (!wxDirExists(cmpLine.fileDescrLeft.fullName)) + throw FileError(wxString(_("Error: Source directory does not exist anymore: ")) + wxT("\"") + cmpLine.fileDescrLeft.fullName + wxT("\"")); + createDirectory(target); + break; + case SyncConfiguration::SYNC_DIR_NONE: + return false; + default: + assert (false); + } + break; + + case FILE_RIGHT_SIDE_ONLY: + switch (config.exRightSideOnly) + { + case SyncConfiguration::SYNC_DIR_LEFT: //create folders on left + target = cmpLine.fileDescrLeft.directory + cmpLine.fileDescrRight.relativeName; + statusUpdater->updateStatusText(wxString(_("Creating folder ")) + optionalLineBreak + wxT("\"") + target + wxT("\"")); + + //some check to catch the error that directory on source has been deleted externally after the "compare"... + if (!wxDirExists(cmpLine.fileDescrRight.fullName)) + throw FileError(wxString(_("Error: Source directory does not exist anymore: ")) + wxT("\"") + cmpLine.fileDescrRight.fullName + wxT("\"")); + createDirectory(target); + break; + case SyncConfiguration::SYNC_DIR_RIGHT: //delete folders on right + statusUpdater->updateStatusText(wxString(_("Deleting folder ")) + optionalLineBreak + wxT("\"") + cmpLine.fileDescrRight.fullName + wxT("\"")); + removeDirectory(cmpLine.fileDescrRight.fullName, useRecycleBin); + break; + case SyncConfiguration::SYNC_DIR_NONE: + return false; + default: + assert (false); + } + break; + + case FILE_EQUAL: + return false; + case FILE_RIGHT_NEWER: + case FILE_LEFT_NEWER: + case FILE_DIFFERENT: + default: + assert (false); + } + return true; +} + + +inline +bool deletionImminent(const FileCompareLine& line, const SyncConfiguration& config) +{ //test if current sync-line will result in deletion of files -> used to avoid disc space bottlenecks + if ( (line.cmpResult == FILE_LEFT_SIDE_ONLY && config.exLeftSideOnly == SyncConfiguration::SYNC_DIR_LEFT) || + (line.cmpResult == FILE_RIGHT_SIDE_ONLY && config.exRightSideOnly == SyncConfiguration::SYNC_DIR_RIGHT)) + return true; + else + return false; +} + + +class AlwaysWriteToGrid //this class ensures, that the result of the method below is ALWAYS written on exit, even if exceptions are thrown! +{ +public: + AlwaysWriteToGrid(FileCompareResult& grid) : + gridToWrite(grid) + {} + + ~AlwaysWriteToGrid() + { + removeRowsFromVector(gridToWrite, rowsProcessed); + } + + void rowProcessedSuccessfully(int nr) + { + rowsProcessed.insert(nr); + } + +private: + FileCompareResult& gridToWrite; + set<int> rowsProcessed; +}; + + +//synchronizes while processing rows in grid and returns all rows that have not been synced +void SyncProcess::startSynchronizationProcess(FileCompareResult& grid, const SyncConfiguration& config) throw(AbortThisProcess) +{ + assert (statusUpdater); + +#ifndef __WXDEBUG__ + wxLogNull noWxLogs; //prevent wxWidgets logging +#endif + + AlwaysWriteToGrid writeOutput(grid); //ensure that grid is always written to, even if method is exitted via exceptions + + //inform about the total amount of data that will be processed from now on + int objectsToCreate = 0; + int objectsToOverwrite = 0; + int objectsToDelete = 0; + double dataToProcess = 0; + calcTotalBytesToSync(objectsToCreate, + objectsToOverwrite, + objectsToDelete, + dataToProcess, + grid, + config); + + statusUpdater->initNewProcess(objectsToCreate + objectsToOverwrite + objectsToDelete, dataToProcess, StatusHandler::PROCESS_SYNCHRONIZING); + + try + { + // it should never happen, that a directory on left side has same name as file on right side. startCompareProcess() should take care of this + // and split into two "exists on one side only" cases + // Note: this case is not handled by this tool as this is considered to be a bug and must be solved by the user + + //synchronize folders: + for (FileCompareResult::const_iterator i = grid.begin(); i != grid.end(); ++i) + { + if ( i->fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY || + i->fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) + { + + while (true) + { //trigger display refresh + statusUpdater->requestUiRefresh(); + + try + { + if (synchronizeFolder(*i, config)) + //progress indicator update + //indicator is updated only if directory is synched correctly (and if some sync was done)! + statusUpdater->updateProcessedData(1, 0); //each call represents one processed file/directory + + writeOutput.rowProcessedSuccessfully(i - grid.begin()); + break; + } + catch (FileError& error) + { + //if (updateClass) -> is mandatory + ErrorHandler::Response rv = statusUpdater->reportError(error.show()); + + if ( rv == ErrorHandler::CONTINUE_NEXT) + break; + else if (rv == ErrorHandler::RETRY) + ; //continue with loop + else + assert (false); + } + } + } + } + + //synchronize files: + bool deleteLoop = true; + for (int k = 0; k < 2; ++k) //loop over all files twice; reason: first delete, then copy (no sorting necessary: performance) + { + deleteLoop = !k; //-> first loop: delete files, second loop: copy files + + for (FileCompareResult::const_iterator i = grid.begin(); i != grid.end(); ++i) + { + if ( i->fileDescrLeft.objType == FileDescrLine::TYPE_FILE || + i->fileDescrRight.objType == FileDescrLine::TYPE_FILE) + { + if ( deleteLoop && deletionImminent(*i, config) || + !deleteLoop && !deletionImminent(*i, config)) + { + while (true) + { //trigger display refresh + statusUpdater->requestUiRefresh(); + + try + { + if (synchronizeFile(*i, config)) + { + //progress indicator update + //indicator is updated only if file is sync'ed correctly (and if some sync was done)! + int objectsToCreate = 0; + int objectsToOverwrite = 0; + int objectsToDelete = 0; + double dataToProcess = 0; + + if (getBytesToTransfer(objectsToCreate, + objectsToOverwrite, + objectsToDelete, + dataToProcess, + *i, + config)) //update status if some work was done (answer is always "yes" in this context) + statusUpdater->updateProcessedData(objectsToCreate + objectsToOverwrite + objectsToDelete, dataToProcess); + } + + writeOutput.rowProcessedSuccessfully(i - grid.begin()); + break; + } + catch (FileError& error) + { + //if (updateClass) -> is mandatory + ErrorHandler::Response rv = statusUpdater->reportError(error.show()); + + if ( rv == ErrorHandler::CONTINUE_NEXT) + break; + else if (rv == ErrorHandler::RETRY) + ; //continue with loop + else + assert (false); + } + } + } + } + } + } + } + catch (const RuntimeException& theException) + { + wxMessageBox(theException.show(), _("An exception occured!"), wxOK | wxICON_ERROR); + return; + } +} + + +//########################################################################################### + + +//handle execution of a method while updating the UI +class UpdateWhileCopying : public UpdateWhileExecuting +{ +public: + UpdateWhileCopying() {} + ~UpdateWhileCopying() {} + + wxString source; + wxString target; + bool success; + wxString errorMessage; + +private: + void longRunner() //virtual method implementation + { + if (!wxCopyFile(source, target, false)) //abort if file exists + { + success = false; + errorMessage = wxString(_("Error copying file ")) + wxT("\"") + source + wxT("\"") + _(" to ") + wxT("\"") + target + wxT("\""); + return; + } + +#ifdef FFS_LINUX //copying files with Linux does not preserve the modification time => adapt after copying + struct stat fileInfo; + if (stat(source.c_str(), &fileInfo) != 0) //read modification time from source file + { + success = false; + errorMessage = wxString(_("Could not retrieve file info for: ")) + wxT("\"") + source + wxT("\""); + return; + } + + utimbuf newTimes; + newTimes.actime = fileInfo.st_mtime; + newTimes.modtime = fileInfo.st_mtime; + + if (utime(target.c_str(), &newTimes) != 0) + { + success = false; + errorMessage = wxString(_("Error adapting modification time of file ")) + wxT("\"") + target + wxT("\""); + return; + } +#endif // FFS_LINUX + + success = true; + } +}; + + +void copyfileMultithreaded(const wxString& source, const wxString& target, StatusHandler* updateClass) +{ + static UpdateWhileCopying copyAndUpdate; //single instantiation: after each execution thread enters wait phase + + copyAndUpdate.waitUntilReady(); + + //longRunner is called from thread, but no mutex needed here, since thread is in waiting state! + copyAndUpdate.source = source; + copyAndUpdate.target = target; + + copyAndUpdate.execute(updateClass); + + //no mutex needed here since longRunner is finished + if (!copyAndUpdate.success) + throw FileError(copyAndUpdate.errorMessage); +} + + +void FreeFileSync::calcTotalBytesToSync(int& objectsToCreate, + int& objectsToOverwrite, + int& objectsToDelete, + double& dataToProcess, + const FileCompareResult& fileCmpResult, + const SyncConfiguration& config) +{ + objectsToCreate = 0; + objectsToOverwrite = 0; + objectsToDelete = 0; + dataToProcess = 0; + + int toCreate = 0; + int toOverwrite = 0; + int toDelete = 0; + double data = 0; + + for (FileCompareResult::const_iterator i = fileCmpResult.begin(); i != fileCmpResult.end(); ++i) + { //only sum up sizes of files AND directories + if (getBytesToTransfer(toCreate, toOverwrite, toDelete, data, *i, config)) + { + objectsToCreate+= toCreate; + objectsToOverwrite+= toOverwrite; + objectsToDelete+= toDelete; + dataToProcess+= data; + } + } +} |