From 222024f07e505617aec93dc4837be2be27d18856 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 16:55:14 +0200 Subject: 1.13 --- FreeFileSync.cpp | 1923 ------------------------------------------------------ 1 file changed, 1923 deletions(-) delete mode 100644 FreeFileSync.cpp (limited to 'FreeFileSync.cpp') diff --git a/FreeFileSync.cpp b/FreeFileSync.cpp deleted file mode 100644 index d7f398df..00000000 --- a/FreeFileSync.cpp +++ /dev/null @@ -1,1923 +0,0 @@ -#include -#include -#include -#include "FreeFileSync.h" -#include "library/globalFunctions.h" -#include -#include "library/resources.h" -#include -#include - -#ifdef FFS_WIN -#include -#endif // FFS_WIN - -#ifdef FFS_LINUX -#include -#endif // FFS_LINUX - - -using namespace globalFunctions; - - -MainConfiguration::MainConfiguration() : - compareVar(CMP_BY_TIME_SIZE), - filterIsActive(false), //do not filter by default - includeFilter(wxT("*")), //include all files/folders - excludeFilter(wxEmptyString), //exclude nothing - useRecycleBin(FreeFileSync::recycleBinExists()), //enable if OS supports it; else user will have to activate first and then get an error message - continueOnError(false) -{} - - -struct FileInfo -{ - wxULongLong fileSize; //unit: bytes! - wxString lastWriteTime; - wxULongLong lastWriteTimeRaw; //unit: seconds! -}; - - -//some special file functions -void generateFileAndFolderDescriptions(DirectoryDescrType& output, const wxString& directory, StatusHandler* updateClass = 0); -void getFileInformation(FileInfo& output, const wxString& filename); - - -//Note: the following lines are a performance optimization for deleting elements from a vector. It is incredibly faster to create a new -//vector and leave specific elements out than to delete row by row and force recopying of most elements for each single deletion (linear vs quadratic runtime) -template -void removeRowsFromVector(vector& grid, const set& rowsToRemove) -{ - vector temp; - int rowToSkip = -1; //keep it an INT! - - set::iterator rowToSkipIndex = rowsToRemove.begin(); - - if (rowToSkipIndex != rowsToRemove.end()) - rowToSkip = *rowToSkipIndex; - - for (int i = 0; i < int(grid.size()); ++i) - { - if (i != rowToSkip) - temp.push_back(grid[i]); - else - { - ++rowToSkipIndex; - if (rowToSkipIndex != rowsToRemove.end()) - rowToSkip = *rowToSkipIndex; - } - } - grid.swap(temp); -} - - -class GetAllFilesFull : public wxDirTraverser -{ -public: - GetAllFilesFull(DirectoryDescrType& output, wxString dirThatIsSearched, StatusHandler* updateClass = 0); - - wxDirTraverseResult OnFile(const wxString& filename); - - wxDirTraverseResult OnDir(const wxString& dirname); - - wxDirTraverseResult OnOpenError(const wxString& openerrorname); - -private: - DirectoryDescrType& m_output; - wxString directory; - int prefixLength; - FileInfo currentFileInfo; - FileDescrLine fileDescr; - StatusHandler* statusUpdater; -}; - - -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 RuntimeException(_("Error when converting int to wxString")); - return result; - } -} - - -void getFileInformation(FileInfo& output, const wxString& filename) -{ -#ifdef FFS_WIN - 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: ")) + wxT("\"") + filename + wxT("\"")); - - FindClose(fileHandle); - - if (FileTimeToLocalFileTime( - &winFileInfo.ftLastWriteTime, //pointer to UTC file time to convert - &localFileTime //pointer to converted file time - ) == 0) - throw RuntimeException(_("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 RuntimeException(_("Error converting FILETIME to SYSTEMTIME")); - - output.lastWriteTime = numberToWxString(time.wYear) + wxT("-") + - formatTime(time.wMonth) + wxT("-") + - formatTime(time.wDay) + wxT(" ") + - formatTime(time.wHour) + wxT(":") + - formatTime(time.wMinute) + wxT(":") + - formatTime(time.wSecond); - - //local time - output.lastWriteTimeRaw = wxULongLong(localFileTime.dwHighDateTime, localFileTime.dwLowDateTime); - - //reduce precision to 1 second (FILETIME has unit 10^-7 s) - output.lastWriteTimeRaw/= 10000000; // <- time is used for comparison only: unit switched to seconds - - output.fileSize = wxULongLong(winFileInfo.nFileSizeHigh, winFileInfo.nFileSizeLow); - -#else - struct stat fileInfo; - if (stat(filename.c_str(), &fileInfo) != 0) - throw FileError(wxString(_("Could not retrieve file info for: ")) + wxT("\"") + filename + wxT("\"")); - - tm* timeinfo; - timeinfo = localtime(&fileInfo.st_mtime); - char buffer [50]; - strftime(buffer,50,"%Y-%m-%d %H:%M:%S",timeinfo); - - //local time - output.lastWriteTime = buffer; - - //UTC time; unit: 1 second - output.lastWriteTimeRaw = fileInfo.st_mtime; - - output.fileSize = fileInfo.st_size; -#endif -} - - -struct MemoryAllocator -{ - MemoryAllocator() - { - buffer1 = new unsigned char[bufferSize]; - buffer2 = new unsigned char[bufferSize]; - } - - ~MemoryAllocator() - { - delete [] buffer1; - delete [] buffer2; - } - - static const unsigned int bufferSize = 1024 * 512; //512 kb seems to be the perfect buffer size - unsigned char* buffer1; - unsigned char* buffer2; -}; - - -bool filesHaveSameContent(const wxString& filename1, const wxString& filename2) -{ - static MemoryAllocator memory; - - wxFFile file1(filename1.c_str(), wxT("rb")); - if (!file1.IsOpened()) - throw FileError(wxString(_("Could not open file: ")) + wxT("\"") + filename1 + wxT("\"")); - - wxFFile file2(filename2.c_str(), wxT("rb")); - if (!file2.IsOpened()) //NO cleanup necessary for wxFFile - throw FileError(wxString(_("Could not open file: ")) + wxT("\"") + filename2 + wxT("\"")); - - do - { - size_t length1 = file1.Read(memory.buffer1, memory.bufferSize); - if (file1.Error()) throw FileError(wxString(_("Error reading file: ")) + wxT("\"") + filename1 + wxT("\"")); - - size_t length2 = file2.Read(memory.buffer2, memory.bufferSize); - if (file2.Error()) throw FileError(wxString(_("Error reading file: ")) + wxT("\"") + filename2 + wxT("\"")); - - if (length1 != length2 || memcmp(memory.buffer1, memory.buffer2, length1) != 0) - return false; - } - while (!file1.Eof()); - - if (!file2.Eof()) - return false; - - return true; -} - - -void generateFileAndFolderDescriptions(DirectoryDescrType& output, const wxString& directory, StatusHandler* updateClass) -{ - assert (updateClass); - - while (true) - { - output.clear(); - - //get all files and folders from directory (and subdirectories) + information - GetAllFilesFull traverser(output, FreeFileSync::getFormattedDirectoryName(directory), updateClass); - wxDir dir(directory); - - if (dir.Traverse(traverser) != (size_t)-1) - break; //traversed successfully - else - { - ErrorHandler::Response rv = updateClass->reportError(wxString(_("Error traversing directory ")) + wxT("\"") + directory + wxT("\"")); - if (rv == ErrorHandler::CONTINUE_NEXT) - break; - else if (rv == ErrorHandler::RETRY) - ; //continue with loop - else - assert (false); - } - } -} - - -//handle execution of a method while updating the UI -class UpdateWhileComparing : public UpdateWhileExecuting -{ -public: - UpdateWhileComparing() {} - ~UpdateWhileComparing() {} - - wxString file1; - wxString file2; - bool success; - wxString errorMessage; - bool result; - -private: - void longRunner() //virtual method implementation - { - try - { - result = filesHaveSameContent(file1, file2); - success = true; - } - catch (FileError& error) - { - success = false; - errorMessage = error.show(); - } - } -}; - - -bool filesHaveSameContentMultithreaded(const wxString& filename1, const wxString& filename2, StatusHandler* updateClass) -{ - static UpdateWhileComparing cmpAndUpdate; //single instantiation: thread enters wait phase after each execution - - cmpAndUpdate.waitUntilReady(); - - //longRunner is called from thread, but no mutex needed here, since thread is in waiting state! - cmpAndUpdate.file1 = filename1; - cmpAndUpdate.file2 = filename2; - - cmpAndUpdate.execute(updateClass); - - //no mutex needed here since longRunner is finished - if (!cmpAndUpdate.success) - throw FileError(cmpAndUpdate.errorMessage); - - return cmpAndUpdate.result; -} - - -void calcTotalDataForCompare(int& objectsTotal, double& dataTotal, const FileCompareResult& grid, const set& rowsToCompare) -{ - dataTotal = 0; - - for (set::iterator i = rowsToCompare.begin(); i != rowsToCompare.end(); ++i) - { - const FileCompareLine& gridline = grid[*i]; - - dataTotal+= gridline.fileDescrLeft.fileSize.ToDouble(); - dataTotal+= gridline.fileDescrRight.fileSize.ToDouble(); - } - - objectsTotal = rowsToCompare.size() * 2; -} - - -struct DescrBufferLine -{ - wxString directoryName; - DirectoryDescrType* directoryDesc; - -#ifdef FFS_WIN - //Windows does NOT distinguish between upper/lower-case - bool operator>(const DescrBufferLine& b ) const - { - return (directoryName.CmpNoCase(b.directoryName) > 0); - } - bool operator<(const DescrBufferLine& b) const - { - return (directoryName.CmpNoCase(b.directoryName) < 0); - } - bool operator==(const DescrBufferLine& b) const - { - return (directoryName.CmpNoCase(b.directoryName) == 0); - } - -#elif defined FFS_LINUX - //Linux DOES distinguish between upper/lower-case - bool operator>(const DescrBufferLine& b ) const - { - return (directoryName.Cmp(b.directoryName) > 0); - } - bool operator<(const DescrBufferLine& b) const - { - return (directoryName.Cmp(b.directoryName) < 0); - } - bool operator==(const DescrBufferLine& b) const - { - return (directoryName.Cmp(b.directoryName) == 0); - } -#else - adapt this -#endif - -}; - - -class DirectoryDescrBuffer //buffer multiple scans of the same directories -{ -public: - ~DirectoryDescrBuffer() - { - //clean up - for (set::iterator i = buffer.begin(); i != buffer.end(); ++i) - delete i->directoryDesc; - } - - const DirectoryDescrType* getDirectoryDescription(const wxString& directory, StatusHandler* statusUpdater) - { - DescrBufferLine bufferEntry; - bufferEntry.directoryName = directory; - - set::iterator entryFound; - if ((entryFound = buffer.find(bufferEntry)) != buffer.end()) - { - //entry found in buffer; return - return entryFound->directoryDesc; - } - else - { - //entry not found; create new one - bufferEntry.directoryDesc = new DirectoryDescrType; - buffer.insert(bufferEntry); //exception safety: insert into buffer right after creation! - - generateFileAndFolderDescriptions(*bufferEntry.directoryDesc, directory, statusUpdater); //exceptions may be thrown! - return bufferEntry.directoryDesc; - } - } - -private: - set buffer; -}; - - -void FreeFileSync::startCompareProcess(const vector& directoryPairsFormatted, - const CompareVariant cmpVar, - FileCompareResult& output, - StatusHandler* statusUpdater) -{ -#ifndef __WXDEBUG__ - wxLogNull noWxLogs; //hide wxWidgets log messages in release build -#endif - assert (statusUpdater); - -//################################################################################################################################################ - - FileCompareResult output_tmp; //write to output not before END of process! - - try - { - //inform about the total amount of data that will be processed from now on - statusUpdater->initNewProcess(-1, 0, StatusHandler::PROCESS_SCANNING); //it's not known how many files will be scanned => -1 objects - - //do basis scan: only result lines of type FILE_UNDEFINED need to be determined - FreeFileSync::performBaseComparison(directoryPairsFormatted, - output_tmp, - statusUpdater); - - if (cmpVar == CMP_BY_TIME_SIZE) - { - for (FileCompareResult::iterator i = output_tmp.begin(); i != output_tmp.end(); ++i) - { - if (i->cmpResult == FILE_UNDEFINED) - { - //last write time may differ by up to 2 seconds (NTFS vs FAT32) - bool sameWriteTime; - if (i->fileDescrLeft.lastWriteTimeRaw < i->fileDescrRight.lastWriteTimeRaw) - sameWriteTime = (i->fileDescrRight.lastWriteTimeRaw - i->fileDescrLeft.lastWriteTimeRaw <= 2); - else - sameWriteTime = (i->fileDescrLeft.lastWriteTimeRaw - i->fileDescrRight.lastWriteTimeRaw <= 2); - - if (sameWriteTime) - { - if (i->fileDescrLeft.fileSize == i->fileDescrRight.fileSize) - i->cmpResult = FILE_EQUAL; - else - i->cmpResult = FILE_DIFFERENT; - } - else - { - if (i->fileDescrLeft.lastWriteTimeRaw < i->fileDescrRight.lastWriteTimeRaw) - i->cmpResult = FILE_RIGHT_NEWER; - else - i->cmpResult = FILE_LEFT_NEWER; - } - } - } - } -//################################################################################################################################################ - else if (cmpVar == CMP_BY_CONTENT) - { - set rowsToCompareBytewise; //compare of file content happens AFTER finding corresponding files - //in order to separate into two processes (scanning and comparing) - - //pre-check: files have different content if they have a different filesize - for (FileCompareResult::iterator i = output_tmp.begin(); i != output_tmp.end(); ++i) - { - if (i->cmpResult == FILE_UNDEFINED) - { - if (i->fileDescrLeft.fileSize != i->fileDescrRight.fileSize) - i->cmpResult = FILE_DIFFERENT; - else - rowsToCompareBytewise.insert(i - output_tmp.begin()); - } - } - - int objectsTotal = 0; - double dataTotal = 0; - calcTotalDataForCompare(objectsTotal, dataTotal, output_tmp, rowsToCompareBytewise); - - statusUpdater->initNewProcess(objectsTotal, dataTotal, StatusHandler::PROCESS_COMPARING_CONTENT); - - set rowsToDelete; //if errors occur during file access and user skips, these rows need to be deleted from result - - //compair files (that have same size) bytewise... - for (set::iterator i = rowsToCompareBytewise.begin(); i != rowsToCompareBytewise.end(); ++i) - { - FileCompareLine& gridline = output_tmp[*i]; - - statusUpdater->updateStatusText(wxString(_("Comparing content of files ")) + wxT("\"") + gridline.fileDescrLeft.relativeName + wxT("\"")); - - //check files that exist in left and right model but have different content - while (true) - { - //trigger display refresh - statusUpdater->requestUiRefresh(); - - try - { - if (filesHaveSameContentMultithreaded(gridline.fileDescrLeft.fullName, gridline.fileDescrRight.fullName, statusUpdater)) - gridline.cmpResult = FILE_EQUAL; - else - gridline.cmpResult = FILE_DIFFERENT; - - statusUpdater->updateProcessedData(2, (gridline.fileDescrLeft.fileSize * 2).ToDouble()); - break; - } - catch (FileError& error) - { - ErrorHandler::Response rv = statusUpdater->reportError(error.show()); - if (rv == ErrorHandler::CONTINUE_NEXT) - { - rowsToDelete.insert(*i); - break; - } - else if (rv == ErrorHandler::RETRY) - ; //continue with loop - else - assert (false); - } - } - } - - //delete invalid rows that have no valid cmpResult - if (rowsToDelete.size() > 0) - removeRowsFromVector(output_tmp, rowsToDelete); - } - else assert(false); - - - statusUpdater->requestUiRefresh(); - } - catch (const RuntimeException& theException) - { - wxMessageBox(theException.show(), _("An exception occured!"), wxOK | wxICON_ERROR); - return; - } - catch (std::bad_alloc& e) - { - wxMessageBox(wxString(_("System out of memory!")) + wxT(" ") + wxString::From8BitData(e.what()), _("An exception occured!"), wxOK | wxICON_ERROR); - return; - } - -//only if everything was processed correctly output is written to! - output_tmp.swap(output); -} - - -void FreeFileSync::performBaseComparison(const vector& directoryPairsFormatted, - FileCompareResult& output, - StatusHandler* statusUpdater) -{ - //buffer accesses to the same directories; useful when multiple folder pairs are used - DirectoryDescrBuffer descriptionBuffer; - - //process one folder pair after each other - for (vector::const_iterator pair = directoryPairsFormatted.begin(); pair != directoryPairsFormatted.end(); ++pair) - { - //retrieve sets of files (with description data) - const DirectoryDescrType* directoryLeft = descriptionBuffer.getDirectoryDescription(pair->leftDirectory, statusUpdater); - const DirectoryDescrType* directoryRight = descriptionBuffer.getDirectoryDescription(pair->rightDirectory, statusUpdater); - - statusUpdater->forceUiRefresh(); - FileCompareLine newline; - - //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()) - { - newline.fileDescrLeft = *i; - newline.fileDescrRight = FileDescrLine(); - newline.fileDescrRight.directory = pair->rightDirectory; - newline.cmpResult = FILE_LEFT_SIDE_ONLY; - output.push_back(newline); - } - - 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()) - { - newline.fileDescrLeft = FileDescrLine(); - newline.fileDescrLeft.directory = pair->leftDirectory; //directory info is needed when creating new directories - newline.fileDescrRight = *j; - newline.cmpResult = FILE_RIGHT_SIDE_ONLY; - output.push_back(newline); - } - //find files/folders that exist in left and right file model - else - { //files... - if (i->objType == FileDescrLine::TYPE_FILE && j->objType == FileDescrLine::TYPE_FILE) - { - newline.fileDescrLeft = *i; - newline.fileDescrRight = *j; - newline.cmpResult = FILE_UNDEFINED; //not yet determined! - output.push_back(newline); - } - //directories... - else if (i->objType == FileDescrLine::TYPE_DIRECTORY && j->objType == FileDescrLine::TYPE_DIRECTORY) - { - newline.fileDescrLeft = *i; - newline.fileDescrRight = *j; - newline.cmpResult = FILE_EQUAL; - output.push_back(newline); - } - //if we have a nameclash between a file and a directory: split into two separate rows - else if (i->objType != j->objType) - { - newline.fileDescrLeft = *i; - newline.fileDescrRight = FileDescrLine(); - newline.fileDescrRight.directory = pair->rightDirectory; - newline.cmpResult = FILE_LEFT_SIDE_ONLY; - output.push_back(newline); - - newline.fileDescrLeft = FileDescrLine(); - newline.fileDescrLeft.directory = pair->leftDirectory; - newline.fileDescrRight = *j; - newline.cmpResult = FILE_RIGHT_SIDE_ONLY; - output.push_back(newline); - } - else assert (false); - } - } - } - statusUpdater->requestUiRefresh(); -} - - -void FreeFileSync::swapGrids(FileCompareResult& grid) -{ - FileDescrLine tmp; - - for (FileCompareResult::iterator i = grid.begin(); i != grid.end(); ++i) - { - //swap compare result - if (i->cmpResult == FILE_LEFT_SIDE_ONLY) - i->cmpResult = FILE_RIGHT_SIDE_ONLY; - else if (i->cmpResult == FILE_RIGHT_SIDE_ONLY) - i->cmpResult = FILE_LEFT_SIDE_ONLY; - else if (i->cmpResult == FILE_RIGHT_NEWER) - i->cmpResult = FILE_LEFT_NEWER; - else if (i->cmpResult == FILE_LEFT_NEWER) - i->cmpResult = FILE_RIGHT_NEWER; - - //swap file descriptors - tmp = i->fileDescrLeft; - i->fileDescrLeft = i->fileDescrRight; - i->fileDescrRight = tmp; - } -} - - -GetAllFilesFull::GetAllFilesFull(DirectoryDescrType& output, wxString dirThatIsSearched, StatusHandler* updateClass) - : m_output(output), directory(dirThatIsSearched), statusUpdater(updateClass) -{ - assert(updateClass); - prefixLength = directory.Len(); -} - - -wxDirTraverseResult GetAllFilesFull::OnFile(const wxString& filename) -{ - fileDescr.fullName = filename; - fileDescr.directory = directory; - fileDescr.relativeName = filename.Mid(prefixLength, wxSTRING_MAXLEN); - while (true) - { - try - { - getFileInformation(currentFileInfo, filename); - break; - } - catch (FileError& error) - { - //if (updateClass) -> is mandatory - ErrorHandler::Response rv = statusUpdater->reportError(error.show()); - if ( rv == ErrorHandler::CONTINUE_NEXT) - return wxDIR_CONTINUE; - else if (rv == ErrorHandler::RETRY) - ; //continue with loop - else - assert (false); - } - } - - fileDescr.lastWriteTime = currentFileInfo.lastWriteTime; - fileDescr.lastWriteTimeRaw = currentFileInfo.lastWriteTimeRaw; - fileDescr.fileSize = currentFileInfo.fileSize; - fileDescr.objType = FileDescrLine::TYPE_FILE; - m_output.insert(fileDescr); - - //update UI/commandline status information - statusUpdater->updateStatusText(wxString(_("Scanning ")) + wxT("\"") + filename + wxT("\"")); //NO performance issue at all - //add 1 element to the progress indicator - statusUpdater->updateProcessedData(1, 0); //NO performance issue at all - //trigger display refresh - statusUpdater->requestUiRefresh(); - - return wxDIR_CONTINUE; -} - - -wxDirTraverseResult GetAllFilesFull::OnDir(const wxString& dirname) -{ -#ifdef FFS_WIN - if ( dirname.EndsWith(wxT("\\RECYCLER")) || - dirname.EndsWith(wxT("\\System Volume Information"))) - return wxDIR_IGNORE; -#endif // FFS_WIN - - fileDescr.fullName = dirname; - fileDescr.directory = directory; - fileDescr.relativeName = dirname.Mid(prefixLength, wxSTRING_MAXLEN); - fileDescr.lastWriteTime = wxEmptyString; //irrelevant for directories - fileDescr.lastWriteTimeRaw = wxULongLong(0); //irrelevant for directories - fileDescr.fileSize = wxULongLong(0); //currently used by getBytesToTransfer - fileDescr.objType = FileDescrLine::TYPE_DIRECTORY; - m_output.insert(fileDescr); - - //update UI/commandline status information - statusUpdater->updateStatusText(wxString(_("Scanning ")) + wxT("\"") + dirname + wxT("\"")); //NO performance issue at all - //add 1 element to the progress indicator - statusUpdater->updateProcessedData(1, 0); //NO performance issue at all - //trigger display refresh - statusUpdater->requestUiRefresh(); - - return wxDIR_CONTINUE; -} - - -wxDirTraverseResult GetAllFilesFull::OnOpenError(const wxString& openerrorname) -{ - wxMessageBox(openerrorname, _("Error")); - return wxDIR_IGNORE; -} - - -//##################################################################################################### -//file functions - -class GetAllFilesSimple : public wxDirTraverser -{ -public: - GetAllFilesSimple(wxArrayString& files, wxArrayString& subDirectories) : - m_files(files), - m_dirs(subDirectories) - {} - - wxDirTraverseResult OnDir(const wxString& dirname) - { - m_dirs.Add(dirname); - return wxDIR_CONTINUE; - } - - wxDirTraverseResult OnFile(const wxString& filename) - { - m_files.Add(filename); - return wxDIR_CONTINUE; - } - - wxDirTraverseResult OnOpenError(const wxString& openerrorname) - { - wxMessageBox(openerrorname, _("Error")); - return wxDIR_IGNORE; - } - -private: - wxArrayString& m_files; - wxArrayString& m_dirs; -}; - - -class RecycleBin -{ -public: - RecycleBin() : - recycleBinAvailable(false) - { -#ifdef FFS_WIN - recycleBinAvailable = true; -#endif // FFS_WIN - } - - ~RecycleBin() {} - - bool recycleBinExists() - { - return recycleBinAvailable; - } - - void moveToRecycleBin(const wxString& filename) - { - if (!recycleBinAvailable) //this method should ONLY be called if recycle bin is available - throw RuntimeException(_("Initialization of Recycle Bin failed! It cannot be used!")); - -#ifdef FFS_WIN - wxString filenameDoubleNull = filename + wxChar(0); - - SHFILEOPSTRUCT fileOp; - fileOp.hwnd = NULL; - fileOp.wFunc = FO_DELETE; - fileOp.pFrom = filenameDoubleNull.c_str(); - fileOp.pTo = NULL; - fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT; - fileOp.fAnyOperationsAborted = false; - fileOp.hNameMappings = NULL; - fileOp.lpszProgressTitle = NULL; - - if (SHFileOperation(&fileOp //pointer to an SHFILEOPSTRUCT structure that contains information the function needs to carry out - ) != 0 || fileOp.fAnyOperationsAborted) throw FileError(wxString(_("Error moving to recycle bin: ")) + wxT("\"") + filename + wxT("\"")); -#endif // FFS_WIN - } - -private: - bool recycleBinAvailable; -}; - - -RecycleBin FreeFileSync::recycler; - - -inline -void FreeFileSync::removeFile(const wxString& filename, const bool useRecycleBin) -{ - if (!wxFileExists(filename)) return; //this is NOT an error situation: the manual deletion relies on it! - - if (useRecycleBin) - { - recycler.moveToRecycleBin(filename); - return; - } - -#ifdef FFS_WIN - if (!SetFileAttributes( - filename.c_str(), //address of filename - FILE_ATTRIBUTE_NORMAL //attributes to set - )) throw FileError(wxString(_("Error deleting file ")) + wxT("\"") + filename + wxT("\"")); -#endif //FFS_WIN - - if (!wxRemoveFile(filename)) - throw FileError(wxString(_("Error deleting file ")) + wxT("\"") + filename + wxT("\"")); -} - - -void FreeFileSync::removeDirectory(const wxString& directory, const bool useRecycleBin) -{ - if (!wxDirExists(directory)) return; //this is NOT an error situation: the manual deletion relies on it! - - if (useRecycleBin) - { - recycler.moveToRecycleBin(directory); - return; - } - - wxArrayString fileList; - wxArrayString dirList; - - { //own scope for directory access to not disturb deletion! - wxDir dir(directory); - - //get all files and directories from current directory (and subdirectories) - GetAllFilesSimple traverser(fileList, dirList); - if (dir.Traverse(traverser) == (size_t)-1) - throw FileError(wxString(_("Error deleting directory ")) + wxT("\"") + directory + wxT("\"")); - } - - for (unsigned int j = 0; j < fileList.GetCount(); ++j) - removeFile(fileList[j], useRecycleBin); - - dirList.Insert(directory, 0); //this directory will be deleted last - - for (int j = int(dirList.GetCount()) - 1; j >= 0 ; --j) - { -#ifdef FFS_WIN - if (!SetFileAttributes( - dirList[j].c_str(), // address of directory name - FILE_ATTRIBUTE_NORMAL // attributes to set - )) throw FileError(wxString(_("Error deleting directory ")) + wxT("\"") + dirList[j] + wxT("\"")); -#endif // FFS_WIN - - if (!wxRmdir(dirList[j])) - throw FileError(wxString(_("Error deleting directory ")) + wxT("\"") + dirList[j] + wxT("\"")); - } -} - - -void FreeFileSync::createDirectory(const wxString& directory, int level) -{ - if (wxDirExists(directory)) - return; - - if (level == 50) //catch endless loop - return; - - //try to create directory - if (wxMkdir(directory)) - return; - - //if not successfull try to create containing folders first - wxString createFirstDir = wxDir(directory).GetName().BeforeLast(GlobalResources::fileNameSeparator); - - //call function recursively - if (createFirstDir.IsEmpty()) return; - createDirectory(createFirstDir, level + 1); - - //now creation should be possible - if (!wxMkdir(directory)) - { - if (level == 0) - throw FileError(wxString(_("Error creating directory ")) + wxT("\"") + directory + wxT("\"")); - } -} - -/* -void FreeFileSync::copyCreatingDirs(const wxString& source, const wxString& target) -{ - wxString targetPath = wxFileName(target).GetPath(); - createDirectory(targetPath); - - if (!wxCopyFile(source, target, false)) //error if file exists - throw FileError(wxString(_("Error copying file ")) + "\"" + source + "\"" + _(" to ") + "\"" + target + "\""); -} -*/ -//########################################################################################### - - -//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 FreeFileSync::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); -} - - -bool FreeFileSync::recycleBinExists() -{ - return recycler.recycleBinExists(); -} - - -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 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; - } - } -} - - -bool FreeFileSync::synchronizeFile(const FileCompareLine& cmpLine, const SyncConfiguration& config, const bool useRecycleBin, StatusHandler* statusUpdater) -{ //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 ")) + 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 ")) + wxT("\"") + cmpLine.fileDescrLeft.fullName + wxT("\"") + - _(" to ") + 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 ")) + wxT("\"") + cmpLine.fileDescrRight.fullName + wxT("\"") + - _(" to ") + wxT("\"") + target + wxT("\"")); - - copyfileMultithreaded(cmpLine.fileDescrRight.fullName, target, statusUpdater); - break; - case SyncConfiguration::SYNC_DIR_RIGHT: //delete files on right - statusUpdater->updateStatusText(wxString(_("Deleting file ")) + 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 ")) + wxT("\"") + cmpLine.fileDescrRight.fullName + wxT("\"") + - _(" overwriting ") + 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 ")) + wxT("\"") + cmpLine.fileDescrLeft.fullName + wxT("\"") + - _(" overwriting ") + 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 FreeFileSync::synchronizeFolder(const FileCompareLine& cmpLine, const SyncConfiguration& config, const bool useRecycleBin, StatusHandler* statusUpdater) -{ //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 ")) + 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 ")) + 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 ")) + 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 ")) + 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; -} - - -wxString FreeFileSync::formatFilesizeToShortString(const wxULongLong& filesize) -{ - return formatFilesizeToShortString(filesize.ToDouble()); -} - - -wxString FreeFileSync::formatFilesizeToShortString(const double filesize) -{ - double 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"); - } - } - } - } - } - - wxString temp; - if (unit == _(" Byte")) //no decimal places in case of bytes - { - double integer = 0; - modf(nrOfBytes, &integer); //get integer part of nrOfBytes - temp = numberToWxString(int(integer)); - } - else - { - nrOfBytes*= 100; //we want up to two decimal places - double integer = 0; - modf(nrOfBytes, &integer); //get integer part of nrOfBytes - - temp = numberToWxString(int(integer)); - - int length = temp.Len(); - - switch (length) - { - case 0: - temp = _("Error"); - break; - case 1: - temp = wxString(wxT("0")) + GlobalResources::decimalPoint + wxT("0") + temp; - break; //0,01 - case 2: - temp = wxString(wxT("0")) + GlobalResources::decimalPoint + temp; - break; //0,11 - case 3: - temp.insert(1, GlobalResources::decimalPoint); - break; //1,11 - case 4: - temp = temp.substr(0, 3); - temp.insert(2, GlobalResources::decimalPoint); - break; //11,1 - case 5: - temp = temp.substr(0, 3); - break; //111 - default: - return _("Error"); - } - } - return (temp + unit); -} - - -void compoundStringToTable(const wxString& compoundInput, const wxChar* delimiter, vector& output) -{ - output.clear(); - wxString input(compoundInput); - - //make sure input ends with delimiter - no problem with empty strings here - if (!input.EndsWith(delimiter)) - input+= delimiter; - - unsigned int indexStart = 0; - unsigned int indexEnd = 0; - while ((indexEnd = input.find(delimiter, indexStart )) != string::npos) - { - if (indexStart != indexEnd) //do not add empty strings - { - wxString newEntry = input.substr(indexStart, indexEnd - indexStart); - - newEntry.Trim(true); //remove whitespace characters from right - newEntry.Trim(false); //remove whitespace characters from left - - if (!newEntry.IsEmpty()) - output.push_back(newEntry); - } - indexStart = indexEnd + 1; - } -} - - -inline -void formatFilterString(wxString& filter) -{ -#ifdef FFS_WIN - //Windows does NOT distinguish between upper/lower-case - filter.MakeLower(); -#elif defined FFS_LINUX - //Linux DOES distinguish between upper/lower-case -//nothing to do here -#else - assert(false); -#endif -} - - -inline -void formatFilenameString(wxString& filename) -{ -#ifdef FFS_WIN - //Windows does NOT distinguish between upper/lower-case - filename.MakeLower(); -#elif defined FFS_LINUX - //Linux DOES distinguish between upper/lower-case -//nothing to do here -#else - assert(false); -#endif -} - - -inline -void mergeVectors(vector& changing, const vector& input) -{ - for (vector::const_iterator i = input.begin(); i != input.end(); ++i) - changing.push_back(*i); -} - - -vector FreeFileSync::compoundStringToFilter(const wxString& filterString) -{ - //delimiters may be ';' or '\n' - vector filterList; - vector filterPreProcessing; - compoundStringToTable(filterString, wxT(";"), filterPreProcessing); - - for (vector::const_iterator i = filterPreProcessing.begin(); i != filterPreProcessing.end(); ++i) - { - vector newEntries; - compoundStringToTable(*i, wxT("\n"), newEntries); - mergeVectors(filterList, newEntries); - } - - return filterList; -} - - -void FreeFileSync::filterCurrentGridData(FileCompareResult& currentGridData, const wxString& includeFilter, const wxString& excludeFilter) -{ - //load filter into vectors of strings - //delimiters may be ';' or '\n' - vector includeList = FreeFileSync::compoundStringToFilter(includeFilter); - vector excludeList = FreeFileSync::compoundStringToFilter(excludeFilter); - - //format entries - for (vector::iterator i = includeList.begin(); i != includeList.end(); ++i) - formatFilterString(*i); - for (vector::iterator i = excludeList.begin(); i != excludeList.end(); ++i) - formatFilterString(*i); - -//############################################################## - - //filter currentGridData - for (FileCompareResult::iterator i = currentGridData.begin(); i != currentGridData.end(); ++i) - { - wxString filenameLeft = i->fileDescrLeft.fullName; - wxString filenameRight = i->fileDescrRight.fullName; - - formatFilenameString(filenameLeft); - formatFilenameString(filenameRight); - - //process include filters - if (i->fileDescrLeft.objType != FileDescrLine::TYPE_NOTHING) - { - bool includedLeft = false; - for (vector::const_iterator j = includeList.begin(); j != includeList.end(); ++j) - if (filenameLeft.Matches(*j)) - { - includedLeft = true; - break; - } - - if (!includedLeft) - { - i->selectedForSynchronization = false; - continue; - } - } - - if (i->fileDescrRight.objType != FileDescrLine::TYPE_NOTHING) - { - bool includedRight = false; - for (vector::const_iterator j = includeList.begin(); j != includeList.end(); ++j) - if (filenameRight.Matches(*j)) - { - includedRight = true; - break; - } - - if (!includedRight) - { - i->selectedForSynchronization = false; - continue; - } - } - - //process exclude filters - bool excluded = false; - for (vector::const_iterator j = excludeList.begin(); j != excludeList.end(); ++j) - if (filenameLeft.Matches(*j) || filenameRight.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) -{ //Formatting is needed since functions in FreeFileSync.cpp expect the directory to end with '\' to be able to split the relative names. - //Also improves usability. - - wxString dirnameTmp = dirname; - dirnameTmp.Trim(true); //remove whitespace characters from right - dirnameTmp.Trim(false); //remove whitespace characters from left - - if (dirnameTmp.IsEmpty()) //an empty string is interpreted as "\"; this is not desired - return wxEmptyString; - - //let wxWidgets do the directory formatting, e.g. replace '/' with '\' for Windows - wxString result = wxDir(dirnameTmp).GetName(); - - result.Append(GlobalResources::fileNameSeparator); - 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 == 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 rowsProcessed; -}; - - -//synchronizes while processing rows in grid and returns all rows that have not been synced -void FreeFileSync::startSynchronizationProcess(FileCompareResult& grid, const SyncConfiguration& config, StatusHandler* statusUpdater, const bool useRecycleBin) -{ - 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 (FreeFileSync::synchronizeFolder(*i, config, useRecycleBin, statusUpdater)) - //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 (FreeFileSync::synchronizeFile(*i, config, useRecycleBin, statusUpdater)) - { - //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; - } -} - - -//add(!) all files and subfolder gridlines that are dependent from the directory -void FreeFileSync::addSubElements(set& subElements, const FileCompareResult& grid, const FileCompareLine& relevantRow) -{ - wxString relevantDirectory; - - if (relevantRow.fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) - relevantDirectory = relevantRow.fileDescrLeft.relativeName + GlobalResources::fileNameSeparator; //fileNameSeparator needed to exclude subfile/dirs only - - else if (relevantRow.fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) - relevantDirectory = relevantRow.fileDescrRight.relativeName + GlobalResources::fileNameSeparator; - - else - return; - - for (FileCompareResult::const_iterator i = grid.begin(); i != grid.end(); ++i) - if ( i->fileDescrLeft.relativeName.StartsWith(relevantDirectory) || - i->fileDescrRight.relativeName.StartsWith(relevantDirectory)) - subElements.insert(i - grid.begin()); -} - - -void FreeFileSync::deleteOnGridAndHD(FileCompareResult& grid, const set& rowsToDelete, ErrorHandler* errorHandler, const bool useRecycleBin) -{ - //remove deleted rows from grid - AlwaysWriteToGrid writeOutput(grid); //ensure that grid is always written to, even if method is exitted via exceptions - - //remove from hd - for (set::iterator i = rowsToDelete.begin(); i != rowsToDelete.end(); ++i) - { - const FileCompareLine& currentCmpLine = grid[*i]; - - while (true) - { - try - { - if (currentCmpLine.fileDescrLeft.objType == FileDescrLine::TYPE_FILE) - FreeFileSync::removeFile(currentCmpLine.fileDescrLeft.fullName, useRecycleBin); - else if (currentCmpLine.fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) - FreeFileSync::removeDirectory(currentCmpLine.fileDescrLeft.fullName, useRecycleBin); - - if (currentCmpLine.fileDescrRight.objType == FileDescrLine::TYPE_FILE) - FreeFileSync::removeFile(currentCmpLine.fileDescrRight.fullName, useRecycleBin); - else if (currentCmpLine.fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) - FreeFileSync::removeDirectory(currentCmpLine.fileDescrRight.fullName, useRecycleBin); - - //remove deleted row from grid - writeOutput.rowProcessedSuccessfully(*i); - - //retrieve all files and subfolder gridlines that are dependent from this deleted entry - set additionalRowsToDelete; - addSubElements(additionalRowsToDelete, grid, grid[*i]); - - //...and remove them also - for (set::iterator j = additionalRowsToDelete.begin(); j != additionalRowsToDelete.end(); ++j) - writeOutput.rowProcessedSuccessfully(*j); - - break; - } - catch (FileError& error) - { - //if (updateClass) -> is mandatory - ErrorHandler::Response rv = errorHandler->reportError(error.show()); - - if (rv == ErrorHandler::CONTINUE_NEXT) - break; - - else if (rv == ErrorHandler::RETRY) - ; //continue in loop - else - assert (false); - } - } - } -} - - -bool FreeFileSync::foldersAreValidForComparison(const vector& folderPairs, wxString& errorMessage) -{ - for (vector::const_iterator i = folderPairs.begin(); i != folderPairs.end(); ++i) - { - const wxString leftFolderName = getFormattedDirectoryName(i->leftDirectory); - const wxString rightFolderName = getFormattedDirectoryName(i->rightDirectory); - - //check if folder name is empty - if (leftFolderName.IsEmpty() || rightFolderName.IsEmpty()) - { - errorMessage = _("Please fill all empty directory fields."); - return false; - } - - //check if folder exists - if (!wxDirExists(leftFolderName)) - { - errorMessage = wxString(_("Directory does not exist: ")) + wxT("\"") + leftFolderName + wxT("\""); - return false; - } - if (!wxDirExists(rightFolderName)) - { - errorMessage = wxString(_("Directory does not exist: ")) + wxT("\"") + rightFolderName + wxT("\""); - return false; - } - } - return true; -} - - -void FreeFileSync::adjustModificationTimes(const wxString& parentDirectory, const int timeInSeconds, ErrorHandler* errorHandler) -{ - if (timeInSeconds == 0) - return; - - wxArrayString fileList; - wxArrayString dirList; - - while (true) //own scope for directory access to not disturb file access! - { - fileList.Clear(); - dirList.Clear(); - - //get all files and folders from directory (and subdirectories) - GetAllFilesSimple traverser(fileList, dirList); - - wxDir dir(parentDirectory); - if (dir.Traverse(traverser) != (size_t)-1) - break; //traversed successfully - else - { - ErrorHandler::Response rv = errorHandler->reportError(wxString(_("Error traversing directory ")) + wxT("\"") + parentDirectory + wxT("\"")); - if (rv == ErrorHandler::CONTINUE_NEXT) - break; - else if (rv == ErrorHandler::RETRY) - ; //continue with loop - else - assert (false); - } - } - - - //this part is only a bit slower than direct Windows API-access! - wxDateTime modTime; - for (unsigned int j = 0; j < fileList.GetCount(); ++j) - { - while (true) //own scope for directory access to not disturb file access! - { - try - { - wxFileName file(fileList[j]); - if (!file.GetTimes(NULL, &modTime, NULL)) //get modification time - throw FileError(wxString(_("Error changing modification time: ")) + wxT("\"") + fileList[j] + wxT("\"")); - - modTime.Add(wxTimeSpan(0, 0, timeInSeconds, 0)); - - if (!file.SetTimes(NULL, &modTime, NULL)) //get modification time - throw FileError(wxString(_("Error changing modification time: ")) + wxT("\"") + fileList[j] + wxT("\"")); - - break; - } - catch (const FileError& error) - { - ErrorHandler::Response rv = errorHandler->reportError(error.show()); - if (rv == ErrorHandler::CONTINUE_NEXT) - break; - else if (rv == ErrorHandler::RETRY) - ; //continue with loop - else - assert (false); - } - } - } -} - - - - - - - - -- cgit