diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 16:44:25 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 16:44:25 +0200 |
commit | c63d9b438572f06f555e2232a15bd3c46bd10546 (patch) | |
tree | 92f2eca00f2a915078ee979acf26906670d75e5f /FreeFileSync.cpp | |
download | FreeFileSync-c63d9b438572f06f555e2232a15bd3c46bd10546.tar.gz FreeFileSync-c63d9b438572f06f555e2232a15bd3c46bd10546.tar.bz2 FreeFileSync-c63d9b438572f06f555e2232a15bd3c46bd10546.zip |
1.2
Diffstat (limited to 'FreeFileSync.cpp')
-rw-r--r-- | FreeFileSync.cpp | 1278 |
1 files changed, 1278 insertions, 0 deletions
diff --git a/FreeFileSync.cpp b/FreeFileSync.cpp new file mode 100644 index 00000000..6269fd60 --- /dev/null +++ b/FreeFileSync.cpp @@ -0,0 +1,1278 @@ +#include <wx/arrstr.h> +#include <wx/dir.h> +#include <wx/msgdlg.h> +#include "FreeFileSync.h" +#include "library\md5.h" +#include <stdexcept> //for std::runtime_error +#include "library\globalfunctions.h" +#include "library\GMP\include\gmp.h" +#include <wx/filename.h> + +using namespace GlobalFunctions; + +inline +wxString formatTime(unsigned int number) +{ + assert (number < 100); + + if (number <= 9) + { + wxChar result[3]; + + *result = '0'; + result[1] = '0' + number; + result[2] = 0; + + return result; + } + else + { + wxString result; + if (result.Printf(wxT("%d"), number) < 0) + throw std::runtime_error("Error when converting int to wxString"); + return result; + } +} + + +bool filetimeCmpSmallerThan(const FILETIME a,const FILETIME b) +{ + if (a.dwHighDateTime != b.dwHighDateTime) + return (a.dwHighDateTime < b.dwHighDateTime); + else + return (a.dwLowDateTime < b.dwLowDateTime); +} + +bool filetimeCmpEqual(const FILETIME a,const FILETIME b) +{ + if (a.dwHighDateTime == b.dwHighDateTime && a.dwLowDateTime == b.dwLowDateTime) + return (true); + else + return (false); +} + +void FreeFileSync::getFileInformation(FileInfo& output, const wxString& filename) +{ + WIN32_FIND_DATA winFileInfo; + FILETIME localFileTime; + SYSTEMTIME time; + HANDLE fileHandle; + + if ((fileHandle = FindFirstFile(filename.c_str(), &winFileInfo)) == INVALID_HANDLE_VALUE) + throw FileError((wxString(_("Could not retrieve file info for: ")) + "\"" + filename.c_str() + "\"").c_str()); + + FindClose(fileHandle); + + /* if (FileTimeToLocalFileTime( + &winFileInfo.ftCreationTime, // pointer to UTC file time to convert + &localFileTime // pointer to converted file time + ) == 0) + throw std::runtime_error("Error converting FILETIME to local FILETIME"); + + if (FileTimeToSystemTime( + &localFileTime, // pointer to file time to convert + &time // pointer to structure to receive system time + ) == 0) + throw std::runtime_error("Error converting FILETIME to SYSTEMTIME"); + + output.creationTime = numberToString(time.wYear) + "." + + formatTime(time.wMonth) + "." + + formatTime(time.wDay) + " " + + formatTime(time.wHour) + ":" + + formatTime(time.wMinute) + ":" + + formatTime(time.wSecond);*/ + +//***************************************************************************** + if (FileTimeToLocalFileTime( + &winFileInfo.ftLastWriteTime, // pointer to UTC file time to convert + &localFileTime // pointer to converted file time + ) == 0) + throw std::runtime_error(_("Error converting FILETIME to local FILETIME")); + + + if (FileTimeToSystemTime( + &localFileTime, // pointer to file time to convert + &time // pointer to structure to receive system time + ) == 0) + throw std::runtime_error(_("Error converting FILETIME to SYSTEMTIME")); + + output.lastWriteTime = numberToWxString(time.wYear) + "-" + + formatTime(time.wMonth) + "-" + + formatTime(time.wDay) + " " + + formatTime(time.wHour) + ":" + + formatTime(time.wMinute) + ":" + + formatTime(time.wSecond); + + //UTC times + output.lastWriteTimeUTC = winFileInfo.ftLastWriteTime; + + mpz_t largeInt; + mpz_init_set_ui(largeInt, winFileInfo.nFileSizeHigh); + mpz_mul_ui(largeInt, largeInt, 65536); + mpz_mul_ui(largeInt, largeInt, 65536); + mpz_add_ui(largeInt, largeInt, winFileInfo.nFileSizeLow); + output.fileSize = mpz_get_str(0, 10, largeInt); + mpz_clear(largeInt); +} + +wxString FreeFileSync::calculateMD5Hash(const wxString& filename) +{ + const unsigned int bufferSize = 4096; + + char md5_output[33]; + unsigned char signature[16]; + unsigned char buffer[bufferSize]; + md5_t state; + + md5_init(&state); + FILE* inputFile = fopen( filename.c_str(), "rb"); + if (!inputFile) + throw FileError((wxString(_("Could not open file: ")) + "\"" + filename + "\"").c_str()); + do + { + unsigned int length = fread(buffer, 1, bufferSize, inputFile); + if (!ferror(inputFile)) md5_process(&state, buffer, length); + } + while (!feof(inputFile)); + + fclose(inputFile); + + md5_finish(&state, signature); + + for (int i=0;i<16;i++) sprintf(md5_output + i*2, "%02x", signature[i]); + + return md5_output; +} + + +void FreeFileSync::generateFileAndFolderDescriptions(DirectoryDescrType& output, const wxString& directory, StatusUpdater* updateClass) +{ + assert (updateClass); + + output.clear(); + + //get all files and folders from directory (and subdirectories) + information + GetAllFilesFull traverser(output, getFormattedDirectoryName(directory), updateClass); + wxDir dir(directory); + dir.Traverse(traverser); +} + + +void FreeFileSync::getModelDiff(FileCompareResult& output, const wxString& dirLeft, const wxString& dirRight, CompareVariant cmpVar, StatusUpdater* updateClass) +{ + assert (updateClass); + + try + { + //retrieve sets of files (with description data) + DirectoryDescrType directoryLeft; + DirectoryDescrType directoryRight; + generateFileAndFolderDescriptions(directoryLeft, dirLeft, updateClass); + generateFileAndFolderDescriptions(directoryRight, dirRight, updateClass); + + FileCompareLine gridLine; + + output.clear(); + + //find files/folders that exist in left file model but not in right model + for (DirectoryDescrType::iterator i = directoryLeft.begin(); i != directoryLeft.end(); i++) + if (directoryRight.find(*i) == directoryRight.end()) + { + gridLine.fileDescrLeft = *i; + gridLine.fileDescrRight = FileDescrLine(); + gridLine.fileDescrRight.directory = dirRight; + gridLine.cmpResult = FileOnLeftSideOnly; + output.push_back(gridLine); + } + + for (DirectoryDescrType::iterator j = directoryRight.begin(); j != directoryRight.end(); j++) + { + DirectoryDescrType::iterator i; + + //find files/folders that exist in right file model but not in left model + if ((i = directoryLeft.find(*j)) == directoryLeft.end()) + { + gridLine.fileDescrLeft = FileDescrLine(); + gridLine.fileDescrLeft.directory = dirLeft; + gridLine.fileDescrRight = *j; + gridLine.cmpResult = FileOnRightSideOnly; + output.push_back(gridLine); + } + //find files that exist in left and right file model + else + { //objType != isNothing + if (i->objType == IsDirectory && j->objType == IsDirectory) + { + gridLine.fileDescrLeft = *i; + gridLine.fileDescrRight = *j; + gridLine.cmpResult = FilesEqual; + output.push_back(gridLine); + } + //if we have a nameclash between a file and a directory: split into separate rows + else if (i->objType != j->objType) + { + gridLine.fileDescrLeft = *i; + gridLine.fileDescrRight = FileDescrLine(); + gridLine.fileDescrRight.directory = dirRight; + gridLine.cmpResult = FileOnLeftSideOnly; + output.push_back(gridLine); + + gridLine.fileDescrLeft = FileDescrLine(); + gridLine.fileDescrLeft.directory = dirLeft; + gridLine.fileDescrRight = *j; + gridLine.cmpResult = FileOnRightSideOnly; + output.push_back(gridLine); + } + else if (cmpVar == CompareByTimeAndSize) + { + //check files that exist in left and right model but have different properties + if (!filetimeCmpEqual(i->lastWriteTimeUTC, j->lastWriteTimeUTC) || + i->fileSize != j->fileSize) + { + gridLine.fileDescrLeft = *i; + gridLine.fileDescrRight = *j; + + if (filetimeCmpEqual(i->lastWriteTimeUTC, j->lastWriteTimeUTC)) + gridLine.cmpResult = FilesDifferent; + else if (filetimeCmpSmallerThan(i->lastWriteTimeUTC, j->lastWriteTimeUTC)) + gridLine.cmpResult = RightFileNewer; + else + gridLine.cmpResult = LeftFileNewer; + output.push_back(gridLine); + } + else + { + gridLine.fileDescrLeft = *i; + gridLine.fileDescrRight = *j; + gridLine.cmpResult = FilesEqual; + output.push_back(gridLine); + } + } + else if (cmpVar == CompareByMD5) + { + while (true) + { + try + { + //check files that exist in left and right model but have different checksums + if (j->fileSize != i->fileSize || calculateMD5Hash(i->filename) != calculateMD5Hash(j->filename)) + { + gridLine.fileDescrLeft = *i; + gridLine.fileDescrRight = *j; + + gridLine.cmpResult = FilesDifferent; + output.push_back(gridLine); + } + else + { + gridLine.fileDescrLeft = *i; + gridLine.fileDescrRight = *j; + gridLine.cmpResult = FilesEqual; + output.push_back(gridLine); + } + break; + } + catch (FileError& error) + { + //if (updateClass) -> is mandatory + int rv = updateClass->reportError(error.show()); + if ( rv == StatusUpdater::Continue) + break; + else if (rv == StatusUpdater::Retry) + ; //continue with loop + else + assert (false); + } + } + } + else assert (false); + } + } + updateClass->triggerUI_Refresh(); + } + catch (std::runtime_error& theException) + { + wxMessageBox(_(theException.what()), _("An exception occured!"), wxOK | wxICON_ERROR); + return; + } +} + + +void FreeFileSync::swapGrids(FileCompareResult& grid) +{ + FileDescrLine tmp; + + for (FileCompareResult::iterator i = grid.begin(); i != grid.end(); ++i) + { + //swap compare result + if (i->cmpResult == FileOnLeftSideOnly) + i->cmpResult = FileOnRightSideOnly; + else if (i->cmpResult == FileOnRightSideOnly) + i->cmpResult = FileOnLeftSideOnly; + else if (i->cmpResult == RightFileNewer) + i->cmpResult = LeftFileNewer; + else if (i->cmpResult == LeftFileNewer) + i->cmpResult = RightFileNewer; + + //swap file descriptors + tmp = i->fileDescrLeft; + i->fileDescrLeft = i->fileDescrRight; + i->fileDescrRight = tmp; + } +} + + +GetAllFilesFull::GetAllFilesFull(DirectoryDescrType& output, wxString dirThatIsSearched, StatusUpdater* updateClass) + : m_output(output), directory(dirThatIsSearched), statusUpdater(updateClass) +{ + assert(updateClass); + prefixLength = directory.Len(); +} + + +wxDirTraverseResult GetAllFilesFull::OnFile(const wxString& filename) +{ + fileDescr.filename = filename; + fileDescr.directory = directory; + fileDescr.relFilename = filename.Mid(prefixLength, wxSTRING_MAXLEN); + while (true) + { + try + { + FreeFileSync::getFileInformation(currentFileInfo, filename); + break; + } + catch (FileError& error) + { + //if (updateClass) -> is mandatory + int rv = statusUpdater->reportError(error.show()); + if ( rv == StatusUpdater::Continue) + return wxDIR_CONTINUE; + else if (rv == StatusUpdater::Retry) + ; //continue with loop + else + assert (false); + } + } + + fileDescr.lastWriteTime = currentFileInfo.lastWriteTime; + fileDescr.lastWriteTimeUTC = currentFileInfo.lastWriteTimeUTC; + fileDescr.fileSize = currentFileInfo.fileSize; + fileDescr.objType = IsFile; + m_output.insert(fileDescr); + + //update UI/commandline status information + statusUpdater->updateStatus(filename); // NO performance issue at all + //add 1 element to the progress indicator + statusUpdater->updateProgressIndicator(1); // NO performance issue at all + //trigger display refresh + statusUpdater->triggerUI_Refresh(); + + return wxDIR_CONTINUE; +} + + +wxDirTraverseResult GetAllFilesFull::OnDir(const wxString& dirname) +{ + if (dirname.EndsWith("\\RECYCLER")) + return wxDIR_IGNORE; + if (dirname.EndsWith("\\System Volume Information")) + return wxDIR_IGNORE; + + fileDescr.filename = dirname; + fileDescr.directory = directory; + fileDescr.relFilename = dirname.Mid(prefixLength, wxSTRING_MAXLEN); + while (true) + { + try + { + FreeFileSync::getFileInformation(currentFileInfo, dirname); + break; + } + catch (FileError& error) + { + //if (updateClass) -> is mandatory + int rv = statusUpdater->reportError(error.show()); + if ( rv == StatusUpdater::Continue) + return wxDIR_IGNORE; + else if (rv == StatusUpdater::Retry) + ; //continue with loop + else + assert (false); + } + } + fileDescr.lastWriteTime = currentFileInfo.lastWriteTime; + fileDescr.lastWriteTimeUTC = currentFileInfo.lastWriteTimeUTC; + fileDescr.fileSize = "0"; //currentFileInfo.fileSize should be "0" as well, but just to be sure... <- isn't needed anyway... + fileDescr.objType = IsDirectory; + m_output.insert(fileDescr); + + //update UI/commandline status information + statusUpdater->updateStatus(dirname); // NO performance issue at all + //add 1 element to the progress indicator + statusUpdater->updateProgressIndicator(1); // NO performance issue at all + //trigger display refresh + statusUpdater->triggerUI_Refresh(); + + return wxDIR_CONTINUE; +} + + +wxDirTraverseResult GetAllFilesFull::OnOpenError(const wxString& openerrorname) +{ + wxMessageBox(openerrorname, _("Error opening file")); + return wxDIR_IGNORE; +} + + +//##################################################################################################### +//file functions + +class GetAllFilesSimple : public wxDirTraverser +{ +public: + GetAllFilesSimple(wxArrayString& files, wxArrayString& subDirectories) : + allFiles(files), + subdirs(subDirectories) + {} + + wxDirTraverseResult OnDir(const wxString& dirname) + { + subdirs.Add(dirname); + return wxDIR_CONTINUE; + } + + wxDirTraverseResult OnFile(const wxString& filename) + { + allFiles.Add(filename); + return wxDIR_CONTINUE; + } + + wxDirTraverseResult OnOpenError(const wxString& openerrorname) + { + wxMessageBox(openerrorname, _("Error opening file")); + return wxDIR_IGNORE; + } + +private: + wxArrayString& allFiles; + wxArrayString& subdirs; +}; + + +void FreeFileSync::moveToRecycleBin(const char* filename) +{ + BOOL aborted = false; + SHFILEOPSTRUCT fileOp; + + char filenameDoubleNull[MAX_PATH + 1]; + strcpy (filenameDoubleNull, filename); + filenameDoubleNull[strlen(filenameDoubleNull) + 1] = 0; + + fileOp.hwnd = NULL; + fileOp.wFunc = FO_DELETE; + fileOp.pFrom = filenameDoubleNull; + fileOp.pTo = NULL; + fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT; + fileOp.fAnyOperationsAborted = aborted; + fileOp.hNameMappings = NULL; + fileOp.lpszProgressTitle = NULL; + + if (fileOperation(&fileOp //Pointer to an SHFILEOPSTRUCT structure that contains information the function needs to carry out. + ) != 0 || aborted) throw FileError(string(_("Error moving file ")) + filename + _(" to recycle bin!")); +} + + +inline +void FreeFileSync::removeFile(const char* filename) +{ + if (!wxFileExists(filename)) return; + + if (recycleBinAvailable) + { + moveToRecycleBin(filename); + return; + } + + if (!SetFileAttributes( + filename, // address of filename + FILE_ATTRIBUTE_NORMAL // address of attributes to set + )) throw FileError(string(_("Error deleting file ")) + filename); + + if (!DeleteFile(filename)) throw FileError(string(_("Error deleting file ")) + filename); +} + + +void FreeFileSync::removeDirectory(const char* directory) +{ + if (!wxDirExists(directory)) return; + + if (recycleBinAvailable) + { + moveToRecycleBin(directory); + return; + } + + wxDir dir(directory); + + //get all files and directories from current directory (and subdirectories) + wxArrayString fileList; + wxArrayString dirList; + GetAllFilesSimple traverser(fileList, dirList); + dir.Traverse(traverser); + + for (unsigned int j = 0; j < fileList.GetCount(); ++j) + removeFile(fileList[j].c_str()); + + dirList.Insert(directory, 0); //this directory will be deleted last + + for (int j = int(dirList.GetCount()) - 1; j >= 0 ; --j) + if (!RemoveDirectory(dirList[j].c_str() // address of directory to remove + )) throw FileError(string(_("Error deleting directory ")) + dirList[j].c_str()); +} + + +inline +void FreeFileSync::copyOverwriting(const char* source, const char* target) +{ + if (!SetFileAttributes( + target, // address of filename + FILE_ATTRIBUTE_NORMAL // address of attributes to set + )) throw FileError(string(_("Error overwriting file ")) + target); + + if (!CopyFile( + source, // pointer to name of an existing file + target, // pointer to filename to copy to + FALSE // overwrite if file exists + )) throw FileError(string(_("Error copying file ")) + source + _(" to ") + target); +} + + +void FreeFileSync::createDirectory(const wxString& directory, int level) +{ + if (wxDirExists(directory)) + return; + + if (level == 50) //catch endless loop + return; + +//try to create directory + if (CreateDirectory( + directory.c_str(), // pointer to a directory path string + NULL // pointer to a security descriptor + )) return; + + //if not successfull try to create containing folders first + wxString createFirstDir = wxDir(directory).GetName().BeforeLast('\\'); + + //call function recursively + if (createFirstDir.IsEmpty()) return; + + createDirectory(createFirstDir, level + 1); + + //now creation should be possible + if (!CreateDirectory( + directory.c_str(), // pointer to a directory path string + NULL // pointer to a security descriptor + )) + { + if (level == 0) + throw FileError(string(_("Error creating directory ")) + directory.c_str()); + } +} + + +void FreeFileSync::copyfile(const char* source, const char* target) +{ + if (!CopyFile( + source, // pointer to name of an existing file + target, // pointer to filename to copy to + TRUE // break if file exists + )) throw FileError(string(_("Error copying file ")) + source + _(" to ") + target); +} + + +void FreeFileSync::copyCreatingDirs(const wxString& source, const wxString& target) +{ + wxString targetPath = wxFileName(target).GetPath(); + createDirectory(targetPath); + + if (!CopyFile( + source.c_str(), // pointer to name of an existing file + target.c_str(), // pointer to filename to copy to + TRUE // break if file exists + )) throw FileError(string(_("Error copying file ")) + source.c_str() + _(" to ") + target.c_str()); +} + +//########################################################################################### + +wxCriticalSection CopyThread::copyFileCritSec; +bool CopyThread::threadIsFinished = true; +bool CopyThread::threadWasSuccessful = true; + +CopyThread::CopyThread(const wxString& sourceFile, const wxString& targetFile) : + source(sourceFile), + target(targetFile) +{} + + +wxThread::ExitCode CopyThread::Entry() +{ + bool success = (CopyFile( + source.c_str(), // pointer to name of an existing file + target.c_str(), // pointer to filename to copy to + TRUE // break if file exists + )); + + copyFileCritSec.Enter(); + + //report status to main thread + threadIsFinished = true; + threadWasSuccessful = success; + + copyFileCritSec.Leave(); + + return 0; +} + + +void FreeFileSync::copyfileMultithreaded(const wxString& source, const wxString& target, StatusUpdater* updateClass) +{ + //at this point threadIsRunning is not shared between threads + CopyThread::threadIsFinished = false; + + //create thread for copying of (large) files + CopyThread* copyFileThread = new CopyThread(source, target); //quite faster than joinable threads + + copyFileThread->Create(); + copyFileThread->SetPriority(80); + copyFileThread->Run(); + + bool processCompleted; + while (true) + { + CopyThread::copyFileCritSec.Enter(); + processCompleted = CopyThread::threadIsFinished; //always put shared data into mutextes/critical sections + CopyThread::copyFileCritSec.Leave(); + + if (processCompleted) //if completed the data is not shared anymore + { + if (!CopyThread::threadWasSuccessful) + { + throw FileError(string(_("Error copying file ")) + source.c_str() + _(" to ") + target.c_str()); + } + else return; + } + + updateClass->triggerUI_Refresh(); + } +} + + +FreeFileSync::FreeFileSync() + : recycleBinAvailable(false) +{ + // Get a handle to the DLL module containing Recycle Bin functionality + hinstShell = LoadLibrary("shell32.dll"); + if (hinstShell == NULL) + recycleBinAvailable = false; + else + { + fileOperation = (DLLFUNC)GetProcAddress(hinstShell, "SHFileOperation"); + if (fileOperation == NULL ) + { + FreeLibrary(hinstShell); + recycleBinAvailable = false; + } + else + recycleBinAvailable = true; + } +} + + +FreeFileSync::~FreeFileSync() +{ + if (recycleBinAvailable) + FreeLibrary(hinstShell); +} + + +bool FreeFileSync::recycleBinExists() +{ + FreeFileSync dummyObject; + return dummyObject.recycleBinAvailable; +} + + +bool FreeFileSync::setRecycleBinUsage(bool activate) +{ //If recycleBin is not available it mustn't be activated: This should NEVER happen! + if (!recycleBinAvailable && activate) + throw std::runtime_error("Initialization of Recycle Bin failed! It cannot be used!"); + else + recycleBinAvailable = activate; + return true; +} + + +inline +SyncDirection getSyncDirection(const CompareFilesResult cmpResult, const SyncConfiguration& config) +{ + switch (cmpResult) + { + case FileOnLeftSideOnly: + return config.exLeftSideOnly; + break; + + case FileOnRightSideOnly: + return config.exRightSideOnly; + break; + + case RightFileNewer: + return config.rightNewer; + break; + + case LeftFileNewer: + return config.leftNewer; + break; + + case FilesDifferent: + return config.different; + break; + + default: + assert (false); + } + return SyncDirNone; +} + + +void FreeFileSync::getBytesToTransfer(mpz_t& result, const FileCompareLine& fileCmpLine, const SyncConfiguration& config) +{ + mpz_set_ui(result, 0); //always initialize variables + + //do not add filtered entries + if (!fileCmpLine.selectedForSynchronization) + return; + + int returnValue = 0; + + switch (fileCmpLine.cmpResult) + { + case FileOnLeftSideOnly: + if (config.exLeftSideOnly == SyncDirRight) + //copy files to right + returnValue = mpz_set_str(result, fileCmpLine.fileDescrLeft.fileSize.c_str(), 10); + break; + + case FileOnRightSideOnly: + if (config.exRightSideOnly == SyncDirLeft) + //copy files to left + returnValue = mpz_set_str(result, fileCmpLine.fileDescrRight.fileSize.c_str(), 10); + break; + + case LeftFileNewer: + case RightFileNewer: + case FilesDifferent: + switch (getSyncDirection(fileCmpLine.cmpResult, config)) + { + case SyncDirLeft: //copy from right to left + returnValue = mpz_set_str(result, fileCmpLine.fileDescrRight.fileSize.c_str(), 10); + break; + case SyncDirRight: //copy from left to right + returnValue = mpz_set_str(result, fileCmpLine.fileDescrLeft.fileSize.c_str(), 10); + break; + case SyncDirNone: + break; + } + break; + + case FilesEqual: + break; + }; + assert (returnValue == 0); +} + + +mpz_class FreeFileSync::calcTotalBytesToTransfer(const FileCompareResult& fileCmpResult, const SyncConfiguration& config) +{ + mpz_t largeTmpInt, result_c; + mpz_init(largeTmpInt); + mpz_init(result_c); + + for (FileCompareResult::const_iterator i = fileCmpResult.begin(); i != fileCmpResult.end(); ++i) + { //only sum up sizes of files, not directories + if (i->fileDescrLeft.objType == IsFile || i->fileDescrRight.objType == IsFile) + { + getBytesToTransfer(largeTmpInt, *i, config); + mpz_add(result_c, result_c, largeTmpInt); //much faster than converting this to mpz_class for each iteration + } + } + + mpz_class result(result_c); + + mpz_clear(largeTmpInt); + mpz_clear(result_c); + + return result; +} + + +void FreeFileSync::synchronizeFile(const FileCompareLine& filename, const SyncConfiguration& config, StatusUpdater* statusUpdater) +{ + assert (statusUpdater); + + if (!filename.selectedForSynchronization) return; + + wxString target; + + //synchronize file: + switch (filename.cmpResult) + { + case FileOnLeftSideOnly: + switch (config.exLeftSideOnly) + { + case SyncDirLeft: //delete files on left + statusUpdater->updateStatus(wxString(_("Deleting file ") + filename.fileDescrLeft.filename)); + removeFile(filename.fileDescrLeft.filename.c_str()); + break; + case SyncDirRight: //copy files to right + target = filename.fileDescrRight.directory + filename.fileDescrLeft.relFilename.c_str(); + statusUpdater->updateStatus(wxString(_("Copying file ")) + filename.fileDescrLeft.filename + + _(" to ") + target); + + copyfileMultithreaded(filename.fileDescrLeft.filename, target, statusUpdater); + break; + case SyncDirNone: + break; + default: + assert (false); + } + break; + + case FileOnRightSideOnly: + switch (config.exRightSideOnly) + { + case SyncDirLeft: //copy files to left + target = filename.fileDescrLeft.directory + filename.fileDescrRight.relFilename; + statusUpdater->updateStatus(wxString(_("Copying file ")) + filename.fileDescrRight.filename + + _(" to ") + target); + + copyfileMultithreaded(filename.fileDescrRight.filename, target, statusUpdater); + break; + case SyncDirRight: //delete files on right + statusUpdater->updateStatus(wxString(_("Deleting file ") + filename.fileDescrRight.filename)); + removeFile(filename.fileDescrRight.filename.c_str()); + break; + case SyncDirNone: + break; + default: + assert (false); + } + break; + + case LeftFileNewer: + case RightFileNewer: + case FilesDifferent: + switch (getSyncDirection(filename.cmpResult, config)) + { + case SyncDirLeft: //copy from right to left + statusUpdater->updateStatus(wxString(_("Copying file ")) + filename.fileDescrRight.filename + + _(" overwriting ") + filename.fileDescrLeft.filename); + + removeFile(filename.fileDescrLeft.filename.c_str()); //only used if switch activated by user, else file is simply deleted + copyfileMultithreaded(filename.fileDescrRight.filename, filename.fileDescrLeft.filename, statusUpdater); + break; + case SyncDirRight: //copy from left to right + statusUpdater->updateStatus(wxString(_("Copying file ")) + filename.fileDescrLeft.filename + + _(" overwriting ") + filename.fileDescrRight.filename); + + removeFile(filename.fileDescrRight.filename.c_str()); //only used if switch activated by user, else file is simply deleted + copyfileMultithreaded(filename.fileDescrLeft.filename, filename.fileDescrRight.filename, statusUpdater); + break; + case SyncDirNone: + break; + default: + assert (false); + } + break; + + case FilesEqual: + break; + + default: + assert (false); + } + return; +} + + +void FreeFileSync::synchronizeFolder(const FileCompareLine& filename, const SyncConfiguration& config, StatusUpdater* statusUpdater) +{ + assert (statusUpdater); + + if (!filename.selectedForSynchronization) return; + + wxString target; + + //synchronize folders: + switch (filename.cmpResult) + { + case FileOnLeftSideOnly: + switch (config.exLeftSideOnly) + { + case SyncDirLeft: //delete folders on left + statusUpdater->updateStatus(wxString(_("Deleting folder ") + filename.fileDescrLeft.filename)); + removeDirectory(filename.fileDescrLeft.filename.c_str()); + break; + case SyncDirRight: //create folders on right + target = filename.fileDescrRight.directory + filename.fileDescrLeft.relFilename; + statusUpdater->updateStatus(wxString(_("Creating folder ") + target)); + + //some check to catch the error that directory on source has been deleted externally after the "compare"... + if (!wxDirExists(filename.fileDescrLeft.filename)) + throw FileError(string(_("Error: Source directory does not exist anymore: ")) + filename.fileDescrLeft.filename.c_str()); + createDirectory(target); + break; + case SyncDirNone: + break; + default: + assert (false); + } + break; + + case FileOnRightSideOnly: + switch (config.exRightSideOnly) + { + case SyncDirLeft: //create folders on left + target = filename.fileDescrLeft.directory + filename.fileDescrRight.relFilename; + statusUpdater->updateStatus(wxString(_("Creating folder ") + target)); + + //some check to catch the error that directory on source has been deleted externally after the "compare"... + if (!wxDirExists(filename.fileDescrRight.filename)) + throw FileError(string(_("Error: Source directory does not exist anymore: ")) + filename.fileDescrRight.filename.c_str()); + createDirectory(target); + break; + case SyncDirRight: //delete folders on right + statusUpdater->updateStatus(wxString(_("Deleting folder ") + filename.fileDescrRight.filename)); + removeDirectory(filename.fileDescrRight.filename.c_str()); + break; + case SyncDirNone: + break; + default: + assert (false); + } + break; + + case FilesEqual: + break; + case RightFileNewer: + case LeftFileNewer: + case FilesDifferent: + default: + assert (false); + } + return; +} + + +wxString FreeFileSync::formatFilesizeToShortString(const mpz_class& filesize) +{ + const char* DigitsSeparator = _("."); + + mpf_class nrOfBytes = filesize; + + wxString unit = " Byte"; + if (nrOfBytes > 999) + { + nrOfBytes/= 1024; + unit = " kB"; + if (nrOfBytes > 999) + { + nrOfBytes/= 1024; + unit = " MB"; + if (nrOfBytes > 999) + { + nrOfBytes/= 1024; + unit = " GB"; + if (nrOfBytes > 999) + { + nrOfBytes/= 1024; + unit = " TB"; + if (nrOfBytes > 999) + { + nrOfBytes/= 1024; + unit = " PB"; + } + } + } + } + } + + mp_exp_t exponent = 0; + + string temp; + if (unit == " Byte") //no decimal places in case of bytes + { + temp = nrOfBytes.get_str(exponent, 10, 3); + if (!temp.length()) //if nrOfBytes is zero GMP returns an empty string + temp = "0"; + } + else + { + temp = string(nrOfBytes.get_str(exponent, 10, 3) + "000").substr(0, 3); //returns mantisse of length 3 in all cases + + if (exponent < 0 || exponent > 3) + temp = _("Error"); + else + switch (exponent) + { + case 0: + temp = string("0") + DigitsSeparator + temp.substr(0, 2); //shorten mantisse as a 0 will be inserted + break; + case 1: + case 2: + temp.insert(exponent, DigitsSeparator); + break; + case 3: + break; + } + } + temp+= unit; + return temp.c_str(); +} + + +void FreeFileSync::filterCurrentGridData(FileCompareResult& currentGridData, const wxString& includeFilter, const wxString& excludeFilter) +{ + wxString includeFilterTmp(includeFilter); + wxString excludeFilterTmp(excludeFilter); + + //make sure filters end with ";" - no problem with empty strings here + if (!includeFilterTmp.EndsWith(";")) + includeFilterTmp.Append(';'); + if (!excludeFilterTmp.EndsWith(";")) + excludeFilterTmp.Append(';'); + + //load filters into vectors + vector<wxString> includeList; + vector<wxString> excludeList; + + unsigned int indexStart = 0; + unsigned int indexEnd = 0; + while ((indexEnd = includeFilterTmp.find(';', indexStart )) != string::npos) + { + if (indexStart != indexEnd) //do not add empty strings + includeList.push_back( includeFilterTmp.substr(indexStart, indexEnd - indexStart) ); + indexStart = indexEnd + 1; + } + + indexStart = 0; + indexEnd = 0; + while ((indexEnd = excludeFilterTmp.find(';', indexStart )) != string::npos) + { + if (indexStart != indexEnd) //do not add empty strings + excludeList.push_back( excludeFilterTmp.substr(indexStart, indexEnd - indexStart) ); + indexStart = indexEnd + 1; + } + +//########################### + + //filter currentGridData + for (FileCompareResult::iterator i = currentGridData.begin(); i != currentGridData.end(); i++) + { + //process include filters + if (i->fileDescrLeft.objType != IsNothing) + { + bool includedLeft = false; + for (vector<wxString>::const_iterator j = includeList.begin(); j != includeList.end(); ++j) + if (i->fileDescrLeft.filename.Matches(*j)) + { + includedLeft = true; + break; + } + + if (!includedLeft) + { + i->selectedForSynchronization = false; + continue; + } + } + + if (i->fileDescrRight.objType != IsNothing) + { + bool includedRight = false; + for (vector<wxString>::const_iterator j = includeList.begin(); j != includeList.end(); ++j) + if (i->fileDescrRight.filename.Matches(*j)) + { + includedRight = true; + break; + } + + if (!includedRight) + { + i->selectedForSynchronization = false; + continue; + } + } + + //process exclude filters + bool excluded = false; + for (vector<wxString>::const_iterator j = excludeList.begin(); j != excludeList.end(); ++j) + if (i->fileDescrLeft.filename.Matches(*j) || + i->fileDescrRight.filename.Matches(*j)) + { + excluded = true; + break; + } + + if (excluded) + { + i->selectedForSynchronization = false; + continue; + } + + i->selectedForSynchronization = true; + } +} + +void FreeFileSync::removeFilterOnCurrentGridData(FileCompareResult& currentGridData) +{ + //remove all filters on currentGridData + for (FileCompareResult::iterator i = currentGridData.begin(); i != currentGridData.end(); i++) + i->selectedForSynchronization = true; +} + + +wxString FreeFileSync::getFormattedDirectoryName(const wxString& dirname) +{ //this formatting is needed since functions in FreeFileSync.cpp expect the directory to end with '\' to be able to split the relative names + //Actually all it needs, is the length of the directory + + //let wxWidgets do the directory formatting, e.g. replace '/' with '\' + wxString result = wxDir(dirname).GetName(); + + result.Append('\\'); + return result; +} + + +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 == FileOnLeftSideOnly && config.exLeftSideOnly == SyncDirLeft) || + (line.cmpResult == FileOnRightSideOnly && config.exRightSideOnly == SyncDirRight)) + return true; + else + return false; +} + + +void FreeFileSync::startSynchronizationProcess(FileCompareResult& grid, const SyncConfiguration& config, StatusUpdater* statusUpdater, bool useRecycleBin) +{ + assert (statusUpdater); + + try + { + FreeFileSync fileSyncObject; //currently only needed for recycle bin + fileSyncObject.setRecycleBinUsage(useRecycleBin); + + FileCompareResult resultsGrid; + + // it should never happen, that a directory on left side has same name as file on right side. GetModelDiff 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 == IsDirectory || i->fileDescrRight.objType == IsDirectory) + { + while (true) + { //trigger display refresh + statusUpdater->triggerUI_Refresh(); + + try + { + fileSyncObject.synchronizeFolder(*i, config, statusUpdater); + break; + } + catch (FileError& error) + { + //if (updateClass) -> is mandatory + int rv = statusUpdater->reportError(error.show()); + + if ( rv == StatusUpdater::Continue) + { + resultsGrid.push_back(*i); //append folders that have not been synced successfully + break; + } + else if (rv == StatusUpdater::Retry) + ; //continue with loop + else + assert (false); + } + } + //progress indicator update + //indicator is updated no matter if errors occured or not! + statusUpdater->updateProgressIndicator(0); //each call represents one processed file/directory + } + } + + //synchronize files: + for (int k = 0; k < 2; ++k) //loop over all files twice; reason: first delete, then copy (no sorting necessary: performance) + { + bool 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 == IsFile || i->fileDescrRight.objType == IsFile) + { + if (deleteLoop && deletionImminent(*i, config) || + !deleteLoop && !deletionImminent(*i, config)) + { + while (true) + { //trigger display refresh + statusUpdater->triggerUI_Refresh(); + + try + { + fileSyncObject.synchronizeFile(*i, config, statusUpdater); + break; + } + catch (FileError& error) + { + //if (updateClass) -> is mandatory + int rv = statusUpdater->reportError(error.show()); + + if ( rv == StatusUpdater::Continue) + { + resultsGrid.push_back(*i); //append files that have not been synced successfully + break; + } + else if (rv == StatusUpdater::Retry) + ; //continue with loop + else + assert (false); + } + } + //progress indicator update + //indicator is updated no matter if errors occured or not! + mpz_t largeTmpInt; + mpz_init(largeTmpInt); + FreeFileSync::getBytesToTransfer(largeTmpInt, *i, config); + statusUpdater->updateProgressIndicator(mpz_get_d(largeTmpInt)); + mpz_clear(largeTmpInt); + } + } + } + } + + //return files that couldn't be processed + grid = resultsGrid; + } + catch (std::runtime_error& theException) + { + wxMessageBox(_(theException.what()), _("An exception occured!"), wxOK | wxICON_ERROR); + return; + } +} + |