summaryrefslogtreecommitdiff
path: root/comparison.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:00:50 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:00:50 +0200
commit4ecfd41e36533d858c98d051ef70cab80e69e972 (patch)
treeca07d8745967d2c6a7123a5d32269cfbfaa7bd6c /comparison.cpp
parent2.2 (diff)
downloadFreeFileSync-4ecfd41e36533d858c98d051ef70cab80e69e972.tar.gz
FreeFileSync-4ecfd41e36533d858c98d051ef70cab80e69e972.tar.bz2
FreeFileSync-4ecfd41e36533d858c98d051ef70cab80e69e972.zip
2.3
Diffstat (limited to 'comparison.cpp')
-rw-r--r--comparison.cpp1156
1 files changed, 624 insertions, 532 deletions
diff --git a/comparison.cpp b/comparison.cpp
index 843990a8..a43a0c86 100644
--- a/comparison.cpp
+++ b/comparison.cpp
@@ -1,4 +1,5 @@
#include "comparison.h"
+#include <stdexcept>
#include "shared/globalFunctions.h"
#include <wx/intl.h>
#include <wx/timer.h>
@@ -13,238 +14,326 @@
#include "shared/systemFunctions.h"
#include "shared/fileTraverser.h"
#include "library/filter.h"
+#include <map>
+#include "fileHierarchy.h"
+#include <boost/bind.hpp>
using namespace FreeFileSync;
-class GetAllFilesFull : public FreeFileSync::TraverseCallback
+std::vector<FreeFileSync::FolderPairCfg> FreeFileSync::extractCompareCfg(const MainConfiguration& mainCfg)
{
+ std::vector<FolderPairCfg> output;
+
+ //add main pair
+ output.push_back(
+ FolderPairCfg(mainCfg.mainFolderPair.leftDirectory,
+ mainCfg.mainFolderPair.rightDirectory,
+ mainCfg.filterIsActive,
+ mainCfg.includeFilter,
+ mainCfg.excludeFilter,
+ mainCfg.syncConfiguration));
+
+ //add additional pairs
+ for (std::vector<FolderPairEnh>::const_iterator i = mainCfg.additionalPairs.begin(); i != mainCfg.additionalPairs.end(); ++i)
+ output.push_back(
+ FolderPairCfg(i->leftDirectory,
+ i->rightDirectory,
+ mainCfg.filterIsActive,
+ i->altFilter.get() ? i->altFilter->includeFilter : mainCfg.includeFilter,
+ i->altFilter.get() ? i->altFilter->excludeFilter : mainCfg.excludeFilter,
+ i->altSyncConfig.get() ? i->altSyncConfig->syncConfiguration : mainCfg.syncConfiguration));
+
+ return output;
+}
+
+
+
+
+
+
+
+template <bool filterActive>
+class BaseDirCallback;
+
+
+template <bool filterActive>
+class DirCallback : public FreeFileSync::TraverseCallback
+{
+public:
+ DirCallback(BaseDirCallback<filterActive>* baseCallback,
+ const Zstring& relNameParentPf, //postfixed with FILE_NAME_SEPARATOR!
+ DirContainer& output,
+ StatusHandler* handler) :
+ baseCallback_(baseCallback),
+ relNameParentPf_(relNameParentPf),
+ output_(output),
+ statusHandler(handler) {}
+
+ virtual ~DirCallback() {}
+
+ virtual ReturnValue onFile(const DefaultChar* shortName, const Zstring& fullName, const FileInfo& details);
+ virtual ReturnValDir onDir(const DefaultChar* shortName, const Zstring& fullName);
+ virtual ReturnValue onError(const wxString& errorText);
+
+private:
+ BaseDirCallback<filterActive>* const baseCallback_;
+ const Zstring relNameParentPf_;
+ DirContainer& output_;
+ StatusHandler* const statusHandler;
+};
+
+
+
+template <bool filterActive>
+class BaseDirCallback : public DirCallback<filterActive>
+{
+ friend class DirCallback<filterActive>;
public:
- GetAllFilesFull(DirectoryDescrType& output, const Zstring& dirThatIsSearched, const FilterProcess* filter, StatusHandler* handler) :
- m_output(output),
- directory(dirThatIsSearched),
+ BaseDirCallback(DirContainer& output, const FilterProcess* filter, StatusHandler* handler) :
+ DirCallback<filterActive>(this, Zstring(), output, handler),
textScanning(Zstring(_("Scanning:")) + wxT(" \n")),
- filterInstance(filter),
- statusHandler(handler)
- {
- prefixLength = directory.length();
- }
+ filterInstance(filter) {}
+ virtual TraverseCallback::ReturnValDir onDir(const DefaultChar* shortName, const Zstring& fullName);
- inline
- void writeText(const wxChar* text, const int length, wxChar*& currentPos)
- {
- memcpy(currentPos, text, length * sizeof(wxChar));
- currentPos += length;
- }
+private:
+ typedef boost::shared_ptr<const DirCallback<filterActive> > CallbackPointer;
+ const Zstring textScanning;
+ std::vector<CallbackPointer> callBackBox; //collection of callback pointers to handle ownership
+ const FilterProcess* const filterInstance; //must NOT be NULL if (filterActive)
+};
- virtual ReturnValue onFile(const DefaultChar* shortName, const Zstring& fullName, const FileInfo& details)
- {
- //apply filter before processing (use relative name!)
- if (filterInstance)
- if ( !filterInstance->matchesFileFilterIncl(fullName.c_str() + prefixLength) ||
- filterInstance->matchesFileFilterExcl(fullName.c_str() + prefixLength))
- {
- statusHandler->requestUiRefresh();
- return TRAVERSING_CONTINUE;
- }
- FileDescrLine fileDescr;
- fileDescr.fullName = fullName;
- fileDescr.relativeName = fullName.zsubstr(prefixLength);
- fileDescr.lastWriteTimeRaw = details.lastWriteTimeRaw;
- fileDescr.fileSize = details.fileSize;
- fileDescr.objType = FileDescrLine::TYPE_FILE;
- m_output.push_back(fileDescr);
-
- //assemble status message (performance optimized) = textScanning + wxT("\"") + fullName + wxT("\"")
- const unsigned int statusTextMaxLen = 2000;
- wxChar statusText[statusTextMaxLen];
- wxChar* position = statusText;
- if (textScanning.length() + fullName.length() + 2 < statusTextMaxLen) //leave room for 0 terminating char!
+template <bool filterActive>
+TraverseCallback::ReturnValue DirCallback<filterActive>::onFile(const DefaultChar* shortName, const Zstring& fullName, const FileInfo& details)
+{
+ //apply filter before processing (use relative name!)
+ if (filterActive)
+ {
+ if (!baseCallback_->filterInstance->passFileFilter(relNameParentPf_ + shortName))
{
- writeText(textScanning.c_str(), textScanning.length(), position);
- writeText(wxT("\""), 1, position);
- writeText(fullName.c_str(), fullName.length(), position);
- writeText(wxT("\""), 1, position);
+ statusHandler->requestUiRefresh();
+ return TRAVERSING_CONTINUE;
}
- *position = 0;
+ }
- //update UI/commandline status information
- statusHandler->updateStatusText(statusText);
- //add 1 element to the progress indicator
- statusHandler->updateProcessedData(1, 0); //NO performance issue at all
- //trigger display refresh
- statusHandler->requestUiRefresh();
+ output_.addSubFile(FileDescriptor(shortName, details.lastWriteTimeRaw, details.fileSize), relNameParentPf_);
- return TRAVERSING_CONTINUE;
- }
+ //assemble status message (performance optimized) = textScanning + wxT("\"") + fullName + wxT("\"")
+ Zstring statusText = baseCallback_->textScanning;
+ statusText.reserve(statusText.length() + fullName.length() + 2);
+ statusText += DefaultChar('\"');
+ statusText += fullName;
+ statusText += DefaultChar('\"');
+
+ //update UI/commandline status information
+ statusHandler->updateStatusText(statusText);
+ //add 1 element to the progress indicator
+ statusHandler->updateProcessedData(1, 0); //NO performance issue at all
+ //trigger display refresh
+ statusHandler->requestUiRefresh();
+ return TRAVERSING_CONTINUE;
+}
- virtual ReturnValDir onDir(const DefaultChar* shortName, const Zstring& fullName)
+
+template <bool filterActive>
+TraverseCallback::ReturnValDir DirCallback<filterActive>::onDir(const DefaultChar* shortName, const Zstring& fullName)
+{
+ using globalFunctions::FILE_NAME_SEPARATOR;
+
+ Zstring relName = relNameParentPf_;
+ relName += shortName;
+
+ //apply filter before processing (use relative name!)
+ if (filterActive)
{
- //apply filter before processing (use relative name!)
- if (filterInstance)
+ bool subObjMightMatch = true;
+ if (!baseCallback_->filterInstance->passDirFilter(relName, &subObjMightMatch))
{
- if (!filterInstance->matchesDirFilterIncl(fullName.c_str() + prefixLength))
- {
- statusHandler->requestUiRefresh(); //if not included: CONTINUE traversing subdirs
- return ReturnValDir(ReturnValDir::Continue(), this);
- }
- else if (filterInstance->matchesDirFilterExcl(fullName.c_str() + prefixLength))
+ statusHandler->requestUiRefresh();
+
+ if (subObjMightMatch)
{
- statusHandler->requestUiRefresh(); //if excluded: do NOT traverse subdirs
- return ReturnValDir::Ignore();
+ DirContainer& subDir = output_.addSubDir(DirDescriptor(shortName), relNameParentPf_);
+
+ DirCallback* subDirCallback = new DirCallback(baseCallback_, relName += FILE_NAME_SEPARATOR, subDir, statusHandler);
+ baseCallback_->callBackBox.push_back(typename BaseDirCallback<filterActive>::CallbackPointer(subDirCallback)); //handle ownership
+
+ //attention: ensure directory filtering is applied to exclude actually filtered directories
+ return ReturnValDir(ReturnValDir::Continue(), subDirCallback);
}
+ else
+ return ReturnValDir::Ignore(); //do NOT traverse subdirs
}
-#ifdef FFS_WIN
- if ( fullName.EndsWith(wxT("\\RECYCLER")) ||
- fullName.EndsWith(wxT("\\System Volume Information")))
- {
- statusHandler->requestUiRefresh();
- return ReturnValDir::Ignore();
- }
-#endif // FFS_WIN
-
- FileDescrLine fileDescr;
- fileDescr.fullName = fullName;
- fileDescr.relativeName = fullName.zsubstr(prefixLength);
- fileDescr.lastWriteTimeRaw = 0; //irrelevant for directories
- fileDescr.fileSize = 0; //currently used by getBytesToTransfer
- fileDescr.objType = FileDescrLine::TYPE_DIRECTORY;
- m_output.push_back(fileDescr);
-
- //assemble status message (performance optimized) = textScanning + wxT("\"") + fullName + wxT("\"")
- const unsigned int statusTextMaxLen = 2000;
- wxChar statusText[statusTextMaxLen];
- wxChar* position = statusText;
- if (textScanning.length() + fullName.length() + 2 < statusTextMaxLen) //leave room for 0 terminating char!
- {
- writeText(textScanning.c_str(), textScanning.length(), position);
- writeText(wxT("\""), 1, position);
- writeText(fullName.c_str(), fullName.length(), position);
- writeText(wxT("\""), 1, position);
- }
- *position = 0;
+ }
- //update UI/commandline status information
- statusHandler->updateStatusText(statusText);
- //add 1 element to the progress indicator
- statusHandler->updateProcessedData(1, 0); //NO performance issue at all
- //trigger display refresh
- statusHandler->requestUiRefresh();
+ DirContainer& subDir = output_.addSubDir(DirDescriptor(shortName), relNameParentPf_);
- return ReturnValDir(ReturnValDir::Continue(), this);
- }
+ //assemble status message (performance optimized) = textScanning + wxT("\"") + fullName + wxT("\"")
+ Zstring statusText = baseCallback_->textScanning;
+ statusText.reserve(statusText.length() + fullName.length() + 2);
+ statusText += DefaultChar('\"');
+ statusText += fullName;
+ statusText += DefaultChar('\"');
+
+ //update UI/commandline status information
+ statusHandler->updateStatusText(statusText);
+ //add 1 element to the progress indicator
+ statusHandler->updateProcessedData(1, 0); //NO performance issue at all
+ //trigger display refresh
+ statusHandler->requestUiRefresh();
+
+ DirCallback* subDirCallback = new DirCallback(baseCallback_, relName+=FILE_NAME_SEPARATOR, subDir, statusHandler);
+ baseCallback_->callBackBox.push_back(typename BaseDirCallback<filterActive>::CallbackPointer(subDirCallback)); //handle ownership
+
+ return ReturnValDir(ReturnValDir::Continue(), subDirCallback);
+}
- virtual ReturnValue onError(const wxString& errorText)
+
+template <bool filterActive>
+TraverseCallback::ReturnValue DirCallback<filterActive>::onError(const wxString& errorText)
+{
+ while (true)
{
- while (true)
+ switch (statusHandler->reportError(errorText))
{
- switch (statusHandler->reportError(errorText))
- {
- case ErrorHandler::IGNORE_ERROR:
- return TRAVERSING_CONTINUE;
- case ErrorHandler::RETRY:
- break; //I have to admit "retry" is a bit of a fake here... at least the user has opportunity to abort!
- }
+ case ErrorHandler::IGNORE_ERROR:
+ return TRAVERSING_CONTINUE;
+ case ErrorHandler::RETRY:
+ break; //I have to admit "retry" is a bit of a fake here... at least the user has opportunity to abort!
}
-
- return TRAVERSING_CONTINUE;
}
-private:
- DirectoryDescrType& m_output;
- Zstring directory;
- int prefixLength;
- const Zstring textScanning;
- const FilterProcess* const filterInstance; //may be NULL!
- StatusHandler* statusHandler;
-};
+ return TRAVERSING_CONTINUE; //dummy value
+}
-struct DescrBufferLine
+template <bool filterActive>
+TraverseCallback::ReturnValDir BaseDirCallback<filterActive>::onDir(const DefaultChar* shortName, const Zstring& fullName)
{
+//#ifdef FFS_WIN => transparency is more important: just scan every file
+// if ( fullName.EndsWith(wxT("\\RECYCLER")) ||
+// fullName.EndsWith(wxT("\\System Volume Information")))
+// {
+// DirCallback<filterActive>::statusHandler->requestUiRefresh();
+// return TraverseCallback::ReturnValDir::Ignore();
+// }
+//#endif // FFS_WIN
+ return DirCallback<filterActive>::onDir(shortName, fullName);
+}
+
+
+//------------------------------------------------------------------------------------------
+struct DirBufferKey
+{
+ DirBufferKey(const Zstring& dirname,
+ boost::shared_ptr<const FilterProcess>& filterInst) :
+ directoryName(dirname),
+ filterInstance(filterInst) {}
+
Zstring directoryName;
- DirectoryDescrType* directoryDesc;
+ boost::shared_ptr<const FilterProcess> filterInstance; //buffering has to consider filtering!
- bool operator < (const DescrBufferLine& b) const
+ bool operator < (const DirBufferKey& b) const
{
#ifdef FFS_WIN //Windows does NOT distinguish between upper/lower-case
- return (directoryName.CmpNoCase(b.directoryName) < 0);
+ const int rv = directoryName.CmpNoCase(b.directoryName);
#elif defined FFS_LINUX //Linux DOES distinguish between upper/lower-case
- return (directoryName.Cmp(b.directoryName) < 0);
+ const int rv = directoryName.Cmp(b.directoryName);
#endif
+ if (rv != 0)
+ return rv < 0;
+
+ return filterInstance.get() && b.filterInstance.get() ?
+ *filterInstance < *b.filterInstance :
+ filterInstance.get() < b.filterInstance.get(); //at least one of these is NULL
}
};
-class DirectoryDescrBuffer //buffer multiple scans of the same directories
+//------------------------------------------------------------------------------------------
+class CompareProcess::DirectoryBuffer //buffer multiple scans of the same directories
{
public:
- DirectoryDescrBuffer(const bool traverseDirectorySymlinks, const FilterProcess* filter, StatusHandler* statusUpdater) :
+ DirectoryBuffer(const bool traverseDirectorySymlinks, StatusHandler* statusUpdater) :
m_traverseDirectorySymlinks(traverseDirectorySymlinks),
- filterInstance(filter),
m_statusUpdater(statusUpdater) {}
- ~DirectoryDescrBuffer()
- {
- //clean up
- for (std::set<DescrBufferLine>::iterator i = buffer.begin(); i != buffer.end(); ++i)
- delete i->directoryDesc;
- }
+ const DirContainer& getDirectoryDescription(const Zstring& directoryPostfixed, bool filterActive, const wxString& includeFilter, const wxString& excludeFilter);
- DirectoryDescrType* getDirectoryDescription(const Zstring& directoryFormatted)
- {
- DescrBufferLine bufferEntry;
- bufferEntry.directoryName = directoryFormatted;
-
- std::set<DescrBufferLine>::iterator entryFound = buffer.find(bufferEntry);
- if (entryFound != 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!
-
- if (FreeFileSync::dirExists(directoryFormatted)) //folder existence already checked in startCompareProcess(): do not treat as error when arriving here!
- {
- //get all files and folders from directoryFormatted (and subdirectories)
- GetAllFilesFull traverser(*bufferEntry.directoryDesc, directoryFormatted, filterInstance, m_statusUpdater); //exceptions may be thrown!
- traverseFolder(directoryFormatted, m_traverseDirectorySymlinks, &traverser);
- }
+private:
+ typedef boost::shared_ptr<DirContainer> DirBufferValue; //exception safety: avoid memory leak
+ typedef std::map<DirBufferKey, DirBufferValue> BufferType;
- return bufferEntry.directoryDesc;
- }
- }
+ static DirBufferKey createKey(const Zstring& directoryPostfixed, bool filterActive, const wxString& includeFilter, const wxString& excludeFilter);
+ DirContainer& insertIntoBuffer(const DirBufferKey& newKey);
-private:
- std::set<DescrBufferLine> buffer;
+ BufferType buffer;
const bool m_traverseDirectorySymlinks;
- const FilterProcess* const filterInstance; //may be NULL!
StatusHandler* m_statusUpdater;
};
+//------------------------------------------------------------------------------------------
+
+
+DirBufferKey CompareProcess::DirectoryBuffer::createKey(const Zstring& directoryPostfixed, bool filterActive, const wxString& includeFilter, const wxString& excludeFilter)
+{
+ boost::shared_ptr<const FilterProcess> filterInstance(
+ filterActive && FilterProcess(includeFilter, excludeFilter) != FilterProcess::nullFilter() ? //nullfilter: in: '*', ex ''
+ new FilterProcess(includeFilter, excludeFilter) : NULL);
+
+ return DirBufferKey(directoryPostfixed, filterInstance);
+}
-void foldersAreValidForComparison(const std::vector<FolderPair>& folderPairs, StatusHandler* statusUpdater)
+DirContainer& CompareProcess::DirectoryBuffer::insertIntoBuffer(const DirBufferKey& newKey)
+{
+ DirBufferValue baseContainer(new DirContainer);
+ buffer.insert(std::make_pair(newKey, baseContainer));
+
+ if (FreeFileSync::dirExists(newKey.directoryName.c_str())) //folder existence already checked in startCompareProcess(): do not treat as error when arriving here!
+ {
+ std::auto_ptr<TraverseCallback> traverser(newKey.filterInstance.get() ?
+ static_cast<TraverseCallback*>(new BaseDirCallback<true>(*baseContainer.get(), newKey.filterInstance.get(), m_statusUpdater)) :
+ static_cast<TraverseCallback*>(new BaseDirCallback<false>(*baseContainer.get(), NULL, m_statusUpdater)));
+
+ //get all files and folders from directoryPostfixed (and subdirectories)
+ traverseFolder(newKey.directoryName, m_traverseDirectorySymlinks, traverser.get()); //exceptions may be thrown!
+ }
+ return *baseContainer.get();
+}
+
+
+const DirContainer& CompareProcess::DirectoryBuffer::getDirectoryDescription(
+ const Zstring& directoryPostfixed,
+ bool filterActive,
+ const wxString& includeFilter,
+ const wxString& excludeFilter)
+{
+ const DirBufferKey searchKey = createKey(directoryPostfixed, filterActive, includeFilter, excludeFilter);
+
+ BufferType::const_iterator entryFound = buffer.find(searchKey);
+ if (entryFound != buffer.end())
+ return *entryFound->second.get(); //entry found in buffer; return
+ else
+ return insertIntoBuffer(searchKey); //entry not found; create new one
+}
+
+
+//------------------------------------------------------------------------------------------
+void foldersAreValidForComparison(const std::vector<FolderPairCfg>& folderPairsForm, StatusHandler* statusUpdater)
{
bool checkEmptyDirnameActive = true; //check for empty dirs just once
const wxString additionalInfo = _("You can ignore the error to consider not existing directories as empty.");
- for (std::vector<FolderPair>::const_iterator i = folderPairs.begin(); i != folderPairs.end(); ++i)
+ for (std::vector<FolderPairCfg>::const_iterator i = folderPairsForm.begin(); i != folderPairsForm.end(); ++i)
{
- const Zstring leftFolderName = getFormattedDirectoryName(i->leftDirectory);
- const Zstring rightFolderName = getFormattedDirectoryName(i->rightDirectory);
-
//check if folder name is empty
- if (leftFolderName.empty() || rightFolderName.empty())
+ if (i->leftDirectory.empty() || i->rightDirectory.empty())
{
if (checkEmptyDirnameActive)
{
@@ -264,11 +353,11 @@ void foldersAreValidForComparison(const std::vector<FolderPair>& folderPairs, St
}
//check if folders exist
- if (!leftFolderName.empty())
- while (!FreeFileSync::dirExists(leftFolderName))
+ if (!i->leftDirectory.empty())
+ while (!FreeFileSync::dirExists(i->leftDirectory.c_str()))
{
ErrorHandler::Response rv = statusUpdater->reportError(wxString(_("Directory does not exist:")) + wxT("\n") +
- wxT("\"") + leftFolderName + wxT("\"") + wxT("\n\n") +
+ wxT("\"") + i->leftDirectory + wxT("\"") + wxT("\n\n") +
FreeFileSync::getLastErrorFormatted() + wxT(" ") + additionalInfo);
if (rv == ErrorHandler::IGNORE_ERROR)
break;
@@ -278,11 +367,11 @@ void foldersAreValidForComparison(const std::vector<FolderPair>& folderPairs, St
assert (false);
}
- if (!rightFolderName.empty())
- while (!FreeFileSync::dirExists(rightFolderName))
+ if (!i->rightDirectory.empty())
+ while (!FreeFileSync::dirExists(i->rightDirectory.c_str()))
{
ErrorHandler::Response rv = statusUpdater->reportError(wxString(_("Directory does not exist:")) + wxT("\n") +
- wxT("\"") + rightFolderName + wxT("\"") + wxT("\n\n") +
+ wxT("\"") + i->rightDirectory + wxT("\"") + wxT("\n\n") +
FreeFileSync::getLastErrorFormatted() + wxT(" ") + additionalInfo);
if (rv == ErrorHandler::IGNORE_ERROR)
break;
@@ -295,43 +384,40 @@ void foldersAreValidForComparison(const std::vector<FolderPair>& folderPairs, St
}
-bool dependencyExists(const std::vector<Zstring>& folders, const Zstring& newFolder, wxString& warningMessage)
+bool dependencyExists(const std::set<Zstring>& folders, const Zstring& newFolder, wxString& warningMessage)
{
- if (!newFolder.empty()) //empty folders names might be accepted by user
- {
- warningMessage.Clear();
-
- for (std::vector<Zstring>::const_iterator i = folders.begin(); i != folders.end(); ++i)
- if (!i->empty()) //empty folders names might be accepted by user
- if (newFolder.StartsWith(*i) || i->StartsWith(newFolder))
- {
- warningMessage = wxString(_("Directories are dependent! Be careful when setting up synchronization rules:")) + wxT("\n") +
- wxT("\"") + i->c_str() + wxT("\",\n") +
- wxT("\"") + newFolder.c_str() + wxT("\"");
- return true;
- }
- }
+ for (std::set<Zstring>::const_iterator i = folders.begin(); i != folders.end(); ++i)
+ if (newFolder.StartsWith(*i) || i->StartsWith(newFolder))
+ {
+ warningMessage = wxString(_("Directories are dependent! Be careful when setting up synchronization rules:")) + wxT("\n") +
+ wxT("\"") + i->c_str() + wxT("\",\n") +
+ wxT("\"") + newFolder.c_str() + wxT("\"");
+ return true;
+ }
return false;
}
-bool foldersHaveDependencies(const std::vector<FolderPair>& folderPairs, wxString& warningMessage)
+bool foldersHaveDependencies(const std::vector<FolderPairCfg>& folderPairsFrom, wxString& warningMessage)
{
warningMessage.Clear();
- std::vector<Zstring> folders;
- for (std::vector<FolderPair>::const_iterator i = folderPairs.begin(); i != folderPairs.end(); ++i)
+ std::set<Zstring> folders;
+ for (std::vector<FolderPairCfg>::const_iterator i = folderPairsFrom.begin(); i != folderPairsFrom.end(); ++i)
{
- const Zstring leftFolderName = getFormattedDirectoryName(i->leftDirectory);
- const Zstring rightFolderName = getFormattedDirectoryName(i->rightDirectory);
-
- if (dependencyExists(folders, leftFolderName, warningMessage))
- return true;
- folders.push_back(leftFolderName);
+ if (!i->leftDirectory.empty()) //empty folders names might be accepted by user
+ {
+ if (dependencyExists(folders, i->leftDirectory, warningMessage))
+ return true;
+ folders.insert(i->leftDirectory);
+ }
- if (dependencyExists(folders, rightFolderName, warningMessage))
- return true;
- folders.push_back(rightFolderName);
+ if (!i->rightDirectory.empty()) //empty folders names might be accepted by user
+ {
+ if (dependencyExists(folders, i->rightDirectory, warningMessage))
+ return true;
+ folders.insert(i->rightDirectory);
+ }
}
return false;
@@ -341,8 +427,7 @@ bool foldersHaveDependencies(const std::vector<FolderPair>& folderPairs, wxStrin
CompareProcess::CompareProcess(const bool traverseSymLinks,
const unsigned int fileTimeTol,
const bool ignoreOneHourDiff,
- xmlAccess::WarningMessages& warnings,
- const FilterProcess* filter, //may be NULL
+ xmlAccess::OptionalDialogs& warnings,
StatusHandler* handler) :
fileTimeTolerance(fileTimeTol),
ignoreOneHourDifference(ignoreOneHourDiff),
@@ -350,13 +435,7 @@ CompareProcess::CompareProcess(const bool traverseSymLinks,
statusUpdater(handler),
txtComparingContentOfFiles(Zstring(_("Comparing content of files %x")).Replace(wxT("%x"), wxT("\n\"%x\""), false))
{
- descriptionBuffer = new DirectoryDescrBuffer(traverseSymLinks, filter, handler);
-}
-
-
-CompareProcess::~CompareProcess()
-{
- delete descriptionBuffer;
+ directoryBuffer.reset(new DirectoryBuffer(traverseSymLinks, handler));
}
@@ -364,19 +443,16 @@ struct MemoryAllocator
{
MemoryAllocator()
{
- buffer1 = new unsigned char[bufferSize];
- buffer2 = new unsigned char[bufferSize];
+ buffer = new unsigned char[bufferSize];
}
~MemoryAllocator()
{
- delete [] buffer1;
- delete [] buffer2;
+ delete [] buffer;
}
static const unsigned int bufferSize = 512 * 1024; //512 kb seems to be the perfect buffer size
- unsigned char* buffer1;
- unsigned char* buffer2;
+ unsigned char* buffer;
};
@@ -391,26 +467,27 @@ public:
bool filesHaveSameContent(const Zstring& filename1, const Zstring& filename2, CompareCallback* callback)
{
- static MemoryAllocator memory;
+ static MemoryAllocator memory1;
+ static MemoryAllocator memory2;
wxFFile file1(filename1.c_str(), wxT("rb"));
if (!file1.IsOpened())
- throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename1.c_str() + wxT("\""));
+ throw FileError(wxString(_("Error opening file:")) + wxT(" \"") + filename1.c_str() + wxT("\""));
wxFFile file2(filename2.c_str(), wxT("rb"));
if (!file2.IsOpened()) //NO cleanup necessary for (wxFFile) file1
- throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename2.c_str() + wxT("\""));
+ throw FileError(wxString(_("Error opening file:")) + wxT(" \"") + filename2.c_str() + wxT("\""));
wxLongLong bytesCompared;
do
{
- const size_t length1 = file1.Read(memory.buffer1, MemoryAllocator::bufferSize);
+ const size_t length1 = file1.Read(memory1.buffer, MemoryAllocator::bufferSize);
if (file1.Error()) throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename1.c_str() + wxT("\""));
- const size_t length2 = file2.Read(memory.buffer2, MemoryAllocator::bufferSize);
+ const size_t length2 = file2.Read(memory2.buffer, MemoryAllocator::bufferSize);
if (file2.Error()) throw FileError(wxString(_("Error reading file:")) + wxT(" \"") + filename2.c_str() + wxT("\""));
- if (length1 != length2 || memcmp(memory.buffer1, memory.buffer2, length1) != 0)
+ if (length1 != length2 || ::memcmp(memory1.buffer, memory2.buffer, length1) != 0)
return false;
bytesCompared += length1 * 2;
@@ -531,10 +608,63 @@ bool filesHaveSameContentUpdating(const Zstring& filename1, const Zstring& filen
return cmpAndUpdate.sameContent;
}*/
+void formatPair(FolderPairCfg& input)
+{
+ input.leftDirectory = FreeFileSync::getFormattedDirectoryName(input.leftDirectory);
+ input.rightDirectory = FreeFileSync::getFormattedDirectoryName(input.rightDirectory);
+}
+
+
+struct ToBeRemoved
+{
+ bool operator()(const DirMapping& dirObj) const
+ {
+ return !dirObj.selectedForSynchronization && dirObj.subDirs.size() == 0 && dirObj.subFiles.size() == 0;
+ }
+};
+
+
+class RemoveFilteredDirs
+{
+public:
+ RemoveFilteredDirs(const wxString& include, const wxString& exclude) :
+ filterProc(include, exclude) {}
+
+ void operator()(HierarchyObject& hierObj)
+ {
+ //process subdirs recursively
+ std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), *this);
+
+ //filter subdirectories
+ std::for_each(hierObj.subDirs.begin(), hierObj.subDirs.end(), boost::bind(&RemoveFilteredDirs::filterDir, this, _1));
+
+ //remove superfluous directories
+ hierObj.subDirs.erase(std::remove_if(hierObj.subDirs.begin(), hierObj.subDirs.end(), ::ToBeRemoved()), hierObj.subDirs.end());
+ }
+
+ void filterDir(FreeFileSync::DirMapping& dirObj)
+ {
+ const Zstring relName = dirObj.isEmpty<FreeFileSync::LEFT_SIDE>() ?
+ dirObj.getRelativeName<FreeFileSync::RIGHT_SIDE>() :
+ dirObj.getRelativeName<FreeFileSync::LEFT_SIDE>();
+
+ dirObj.selectedForSynchronization = filterProc.passDirFilter(relName, NULL); //subObjMightMatch is always true in this context!
+ }
+
+private:
+ const FilterProcess filterProc;
+};
+
+
+//filters and removes all excluded directories (but keeps those serving as parent folders)
+void filterAndRemoveDirs(BaseDirMapping& baseDir, const wxString& include, const wxString& exclude)
+{
+ RemoveFilteredDirs(include, exclude)(baseDir);
+}
+
-void CompareProcess::startCompareProcess(const std::vector<FolderPair>& directoryPairs,
+void CompareProcess::startCompareProcess(const std::vector<FolderPairCfg>& directoryPairs,
const CompareVariant cmpVar,
- const SyncConfiguration& config,
FolderComparison& output)
{
#ifndef __WXDEBUG__
@@ -547,11 +677,8 @@ void CompareProcess::startCompareProcess(const std::vector<FolderPair>& director
statusUpdater->initNewProcess(-1, 0, StatusHandler::PROCESS_SCANNING); //it's not known how many files will be scanned => -1 objects
//format directory pairs: ensure they end with globalFunctions::FILE_NAME_SEPARATOR!
- std::vector<FolderPair> directoryPairsFormatted;
- for (std::vector<FolderPair>::const_iterator i = directoryPairs.begin(); i != directoryPairs.end(); ++i)
- directoryPairsFormatted.push_back(
- FolderPair(FreeFileSync::getFormattedDirectoryName(i->leftDirectory),
- FreeFileSync::getFormattedDirectoryName(i->rightDirectory)));
+ std::vector<FolderPairCfg> directoryPairsFormatted = directoryPairs;
+ std::for_each(directoryPairsFormatted.begin(), directoryPairsFormatted.end(), formatPair);
//-------------------some basic checks:------------------------------------------
@@ -581,26 +708,35 @@ void CompareProcess::startCompareProcess(const std::vector<FolderPair>& director
break;
}
- //actually this is the initial determination
- FreeFileSync::redetermineSyncDirection(config, output_tmp);
+
+ assert (output_tmp.size() == directoryPairsFormatted.size());
+
+ for (FolderComparison::iterator j = output_tmp.begin(); j != output_tmp.end(); ++j)
+ {
+ const FolderPairCfg& fpCfg = directoryPairsFormatted[j - output_tmp.begin()];
+
+ //attention: some filtered directories are still in the comparison result! (see include filter handling!)
+ if (fpCfg.filterIsActive) //let's filter them now... (and remove those that contain excluded elements only)
+ filterAndRemoveDirs(*j, fpCfg.includeFilter, fpCfg.excludeFilter);
+
+ //set sync-direction initially
+ FreeFileSync::redetermineSyncDirection(fpCfg.syncConfiguration, *j);
+ }
//only if everything was processed correctly output is written to!
//note: output mustn't change during this process to be in sync with GUI grid view!!!
output_tmp.swap(output);
}
- catch (const RuntimeException& theException)
- {
- statusUpdater->reportFatalError(theException.show().c_str());
- return; //should be obsolete!
- }
- catch (std::bad_alloc& e)
+ catch (const std::exception& e)
{
- statusUpdater->reportFatalError((wxString(_("System out of memory!")) + wxT(" ") + wxString::From8BitData(e.what())).c_str());
+ if (dynamic_cast<const std::bad_alloc*>(&e) != NULL)
+ statusUpdater->reportFatalError(wxString(_("System out of memory!")) + wxT(" ") + wxString::From8BitData(e.what()));
+ else
+ statusUpdater->reportFatalError(wxString::From8BitData(e.what()));
return; //should be obsolete!
}
}
-
//--------------------assemble conflict descriptions---------------------------
//check for very old dates or dates in the future
@@ -614,7 +750,7 @@ wxString getConflictInvalidDate(const Zstring& fileNameFull, const wxLongLong& u
//check for changed files with same modification date
-wxString getConflictSameDateDiffSize(const FileCompareLine& cmpLine)
+wxString getConflictSameDateDiffSize(const FileMapping& fileObj)
{
//some beautification...
wxString left = wxString(_("Left")) + wxT(": ");
@@ -624,18 +760,18 @@ wxString getConflictSameDateDiffSize(const FileCompareLine& cmpLine)
right.Pad(maxPref - right.length(), wxT(' '), true);
wxString msg = _("Files %x have the same date but a different size!");
- msg.Replace(wxT("%x"), wxString(wxT("\"")) + cmpLine.fileDescrLeft.relativeName.c_str() + wxT("\""));
+ msg.Replace(wxT("%x"), wxString(wxT("\"")) + fileObj.getRelativeName<LEFT_SIDE>() + wxT("\""));
msg += wxT("\n\n");
- msg += left + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(cmpLine.fileDescrLeft.lastWriteTimeRaw,
- cmpLine.fileDescrLeft.fullName) + wxT(" \t") + _("Size") + wxT(": ") + cmpLine.fileDescrLeft.fileSize.ToString() + wxT("\n");
- msg += right + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(cmpLine.fileDescrRight.lastWriteTimeRaw,
- cmpLine.fileDescrRight.fullName) + wxT(" \t") + _("Size") + wxT(": ") + cmpLine.fileDescrRight.fileSize.ToString();
+ msg += left + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(fileObj.getLastWriteTime<LEFT_SIDE>(),
+ fileObj.getFullName<LEFT_SIDE>()) + wxT(" \t") + _("Size") + wxT(": ") + fileObj.getFileSize<LEFT_SIDE>().ToString() + wxT("\n");
+ msg += right + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(fileObj.getLastWriteTime<RIGHT_SIDE>(),
+ fileObj.getFullName<RIGHT_SIDE>()) + wxT(" \t") + _("Size") + wxT(": ") + fileObj.getFileSize<RIGHT_SIDE>().ToString();
return wxString(_("Conflict detected:")) + wxT("\n") + msg;
}
//check for files that have a difference in file modification date below 1 hour when DST check is active
-wxString getConflictChangeWithinHour(const FileCompareLine& cmpLine)
+wxString getConflictChangeWithinHour(const FileMapping& fileObj)
{
//some beautification...
wxString left = wxString(_("Left")) + wxT(": ");
@@ -644,19 +780,16 @@ wxString getConflictChangeWithinHour(const FileCompareLine& cmpLine)
left.Pad(maxPref - left.length(), wxT(' '), true);
right.Pad(maxPref - right.length(), wxT(' '), true);
- wxString msg = _("Files %x have a file time difference of less than 1 hour! It's not safe to decide which one is newer due to Daylight Saving Time issues.");
- msg.Replace(wxT("%x"), wxString(wxT("\"")) + cmpLine.fileDescrLeft.relativeName.c_str() + wxT("\""));
+ wxString msg = _("Files %x have a file time difference of less than 1 hour!\n\nIt's not safe to decide which one is newer due to Daylight Saving Time issues.");
+ msg += wxString(wxT("\n")) + _("(Note that only FAT/FAT32 drives are affected by this problem!\nIn all other cases you can disable the setting \"ignore 1-hour difference\".)");
+ msg.Replace(wxT("%x"), wxString(wxT("\"")) + fileObj.getRelativeName<LEFT_SIDE>() + wxT("\""));
msg += wxT("\n\n");
- msg += left + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(cmpLine.fileDescrLeft.lastWriteTimeRaw, cmpLine.fileDescrLeft.fullName) + wxT("\n");
- msg += right + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(cmpLine.fileDescrRight.lastWriteTimeRaw, cmpLine.fileDescrRight.fullName);
+ msg += left + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(fileObj.getLastWriteTime<LEFT_SIDE>(), fileObj.getFullName<LEFT_SIDE>()) + wxT("\n");
+ msg += right + wxT("\t") + _("Date") + wxT(": ") + utcTimeToLocalString(fileObj.getLastWriteTime<RIGHT_SIDE>(), fileObj.getFullName<RIGHT_SIDE>());
return wxString(_("Conflict detected:")) + wxT("\n") + msg;
}
-//-----------------------------------------------------------------------------
-
-
-const CompareFilesResult FILE_UNDEFINED = CompareFilesResult(42);
-
+//-----------------------------------------------------------------------------
inline
bool sameFileTime(const wxLongLong& a, const wxLongLong& b, const unsigned tolerance)
{
@@ -667,385 +800,344 @@ bool sameFileTime(const wxLongLong& a, const wxLongLong& b, const unsigned toler
}
-void CompareProcess::compareByTimeSize(const std::vector<FolderPair>& directoryPairsFormatted, FolderComparison& output)
+void CompareProcess::compareByTimeSize(const std::vector<FolderPairCfg>& directoryPairsFormatted, FolderComparison& output)
{
//process one folder pair after each other
- for (std::vector<FolderPair>::const_iterator pair = directoryPairsFormatted.begin(); pair != directoryPairsFormatted.end(); ++pair)
+ for (std::vector<FolderPairCfg>::const_iterator pair = directoryPairsFormatted.begin(); pair != directoryPairsFormatted.end(); ++pair)
{
- FolderCompareLine newEntry;
- newEntry.syncPair = *pair;
+ BaseDirMapping newEntry(pair->leftDirectory, pair->rightDirectory);
output.push_back(newEntry); //attention: push_back() copies by value!!! performance: append BEFORE writing values into fileCmp!
- FileComparison& fileCmp = output.back().fileCmp;
+ //do basis scan and retrieve files existing on both sides
+ std::vector<FileMapping*> compareCandidates;
+ performBaseComparison(*pair, output.back(), compareCandidates);
- //do basis scan: only result lines of type FILE_UNDEFINED (files that exist on both sides) need to be determined after this call
- this->performBaseComparison(*pair, fileCmp);
+ //PERF_START;
//categorize files that exist on both sides
- for (FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i)
- if (i->cmpResult == FILE_UNDEFINED)
+ for (std::vector<FileMapping*>::iterator i = compareCandidates.begin(); i != compareCandidates.end(); ++i)
+ {
+ FileMapping* const line = *i;
+ if (line->getLastWriteTime<LEFT_SIDE>() != line->getLastWriteTime<RIGHT_SIDE>())
{
- if (i->fileDescrLeft.lastWriteTimeRaw != i->fileDescrRight.lastWriteTimeRaw)
+ //number of seconds since Jan 1st 1970 + 1 year (needn't be too precise)
+ static const long oneYearFromNow = wxGetUTCTime() + 365 * 24 * 3600;
+
+ //check for erroneous dates (but only if dates are not (EXACTLY) the same)
+ if ( line->getLastWriteTime<LEFT_SIDE>() < 0 || //earlier than Jan 1st 1970
+ line->getLastWriteTime<RIGHT_SIDE>() < 0 || //earlier than Jan 1st 1970
+ line->getLastWriteTime<LEFT_SIDE>() > oneYearFromNow || //dated more than one year in future
+ line->getLastWriteTime<RIGHT_SIDE>() > oneYearFromNow) //dated more than one year in future
+ {
+ line->cmpResult = FILE_CONFLICT;
+ if (line->getLastWriteTime<LEFT_SIDE>() < 0 || line->getLastWriteTime<LEFT_SIDE>() > oneYearFromNow)
+ line->conflictDescription = getConflictInvalidDate(line->getFullName<LEFT_SIDE>(), line->getLastWriteTime<LEFT_SIDE>());
+ else
+ line->conflictDescription = getConflictInvalidDate(line->getFullName<RIGHT_SIDE>(), line->getLastWriteTime<RIGHT_SIDE>());
+ }
+ else //from this block on all dates are at least "valid"
{
- //number of seconds since Jan 1st 1970 + 1 year (needn't be too precise)
- static const long oneYearFromNow = wxGetUTCTime() + 365 * 24 * 3600;
-
- //check for erroneous dates (but only if dates are not (EXACTLY) the same)
- if ( i->fileDescrLeft.lastWriteTimeRaw < 0 || //earlier than Jan 1st 1970
- i->fileDescrRight.lastWriteTimeRaw < 0 || //earlier than Jan 1st 1970
- i->fileDescrLeft.lastWriteTimeRaw > oneYearFromNow || //dated more than one year in future
- i->fileDescrRight.lastWriteTimeRaw > oneYearFromNow) //dated more than one year in future
+ //last write time may differ by up to 2 seconds (NTFS vs FAT32)
+ if (sameFileTime(line->getLastWriteTime<LEFT_SIDE>(), line->getLastWriteTime<RIGHT_SIDE>(), fileTimeTolerance))
{
- i->cmpResult = FILE_CONFLICT;
- if (i->fileDescrLeft.lastWriteTimeRaw < 0 || i->fileDescrLeft.lastWriteTimeRaw > oneYearFromNow)
- i->conflictDescription = OptionalString(getConflictInvalidDate(i->fileDescrLeft.fullName, i->fileDescrLeft.lastWriteTimeRaw));
+ if (line->getFileSize<LEFT_SIDE>() == line->getFileSize<RIGHT_SIDE>())
+ line->cmpResult = FILE_EQUAL;
else
- i->conflictDescription = OptionalString(getConflictInvalidDate(i->fileDescrRight.fullName, i->fileDescrRight.lastWriteTimeRaw));
+ {
+ line->cmpResult = FILE_CONFLICT; //same date, different filesize
+ line->conflictDescription = getConflictSameDateDiffSize(*line);
+ }
}
- else //from this block on all dates are at least "valid"
+ else
{
- //last write time may differ by up to 2 seconds (NTFS vs FAT32)
- if (sameFileTime(i->fileDescrLeft.lastWriteTimeRaw, i->fileDescrRight.lastWriteTimeRaw, fileTimeTolerance))
+ //finally: DST +/- 1-hour check: test if time diff is exactly +/- 1-hour (respecting 2 second FAT precision)
+ if (ignoreOneHourDifference && sameFileTime(line->getLastWriteTime<LEFT_SIDE>(), line->getLastWriteTime<RIGHT_SIDE>(), 3600 + 2))
{
- if (i->fileDescrLeft.fileSize == i->fileDescrRight.fileSize)
- i->cmpResult = FILE_EQUAL;
- else
+ //date diff < 1 hour is a conflict: it's not safe to determine which file is newer
+ if (sameFileTime(line->getLastWriteTime<LEFT_SIDE>(), line->getLastWriteTime<RIGHT_SIDE>(), 3600 - 2 - 1))
{
- i->cmpResult = FILE_CONFLICT; //same date, different filesize
- i->conflictDescription = OptionalString(getConflictSameDateDiffSize(*i));
+ line->cmpResult = FILE_CONFLICT;
+ line->conflictDescription = getConflictChangeWithinHour(*line);
}
- }
- else
- {
- //finally: DST +/- 1-hour check: test if time diff is exactly +/- 1-hour (respecting 2 second FAT precision)
- if (ignoreOneHourDifference && sameFileTime(i->fileDescrLeft.lastWriteTimeRaw, i->fileDescrRight.lastWriteTimeRaw, 3600 + 2))
+ else //exact +/- 1-hour detected: treat as equal
{
- //date diff < 1 hour is a conflict: it's not safe to determine which file is newer
- if (sameFileTime(i->fileDescrLeft.lastWriteTimeRaw, i->fileDescrRight.lastWriteTimeRaw, 3600 - 2 - 1))
- {
- i->cmpResult = FILE_CONFLICT;
- i->conflictDescription = OptionalString(getConflictChangeWithinHour(*i));
- }
- else //exact +/- 1-hour detected: treat as equal
+ if (line->getFileSize<LEFT_SIDE>() == line->getFileSize<RIGHT_SIDE>())
+ line->cmpResult = FILE_EQUAL;
+ else
{
- if (i->fileDescrLeft.fileSize == i->fileDescrRight.fileSize)
- i->cmpResult = FILE_EQUAL;
- else
- {
- i->cmpResult = FILE_CONFLICT; //same date, different filesize
- i->conflictDescription = OptionalString(getConflictSameDateDiffSize(*i));
- }
+ line->cmpResult = FILE_CONFLICT; //same date, different filesize
+ line->conflictDescription = getConflictSameDateDiffSize(*line);
}
}
+ }
+ else
+ {
+ if (line->getLastWriteTime<LEFT_SIDE>() < line->getLastWriteTime<RIGHT_SIDE>())
+ line->cmpResult = FILE_RIGHT_NEWER;
else
- {
- if (i->fileDescrLeft.lastWriteTimeRaw < i->fileDescrRight.lastWriteTimeRaw)
- i->cmpResult = FILE_RIGHT_NEWER;
- else
- i->cmpResult = FILE_LEFT_NEWER;
- }
+ line->cmpResult = FILE_LEFT_NEWER;
}
}
}
- else //same write time
+ }
+ else //same write time
+ {
+ if (line->getFileSize<LEFT_SIDE>() == line->getFileSize<RIGHT_SIDE>())
+ line->cmpResult = FILE_EQUAL;
+ else
{
- if (i->fileDescrLeft.fileSize == i->fileDescrRight.fileSize)
- i->cmpResult = FILE_EQUAL;
- else
- {
- i->cmpResult = FILE_CONFLICT; //same date, different filesize
- i->conflictDescription = OptionalString(getConflictSameDateDiffSize(*i));
- }
+ line->cmpResult = FILE_CONFLICT; //same date, different filesize
+ line->conflictDescription = getConflictSameDateDiffSize(*line);
}
}
+ }
}
}
-class RemoveAtExit //this class ensures, that the result of the method below is ALWAYS written on exit, even if exceptions are thrown!
-{
-public:
- RemoveAtExit(FileComparison& fileCmp) :
- gridToWrite(fileCmp) {}
-
- ~RemoveAtExit()
- {
- globalFunctions::removeRowsFromVector(rowsToDelete, gridToWrite);
- }
-
- void markRow(int nr)
- {
- rowsToDelete.insert(nr);
- }
-
-private:
- FileComparison& gridToWrite;
- std::set<int> rowsToDelete;
-};
-
-
-void getBytesToCompare(const FolderComparison& grid, const FolderCompRef& rowsToCompare, int& objectsTotal, wxULongLong& dataTotal)
+wxULongLong getBytesToCompare(const std::vector<FileMapping*>& rowsToCompare)
{
- objectsTotal = 0;
- dataTotal = 0;
-
- for (FolderComparison::const_iterator j = grid.begin(); j != grid.end(); ++j)
- {
- const FileComparison& fileCmp = j->fileCmp;
+ wxULongLong dataTotal;
- const std::set<int>& index = rowsToCompare[j - grid.begin()];
- for (std::set<int>::const_iterator i = index.begin(); i != index.end(); ++i)
- {
- const FileCompareLine& line = fileCmp[*i];
- dataTotal += line.fileDescrLeft.fileSize;
- dataTotal += line.fileDescrRight.fileSize;
- }
+ for (std::vector<FileMapping*>::const_iterator j = rowsToCompare.begin(); j != rowsToCompare.end(); ++j)
+ dataTotal += (*j)->getFileSize<LEFT_SIDE>(); //left and right filesizes should be the same
- objectsTotal += index.size() * 2;
- }
+ return dataTotal * 2;
}
-void CompareProcess::compareByContent(const std::vector<FolderPair>& directoryPairsFormatted, FolderComparison& output)
+void CompareProcess::compareByContent(const std::vector<FolderPairCfg>& directoryPairsFormatted, FolderComparison& output)
{
//PERF_START;
+ std::vector<FileMapping*> compareCandidates;
//process one folder pair after each other
- for (std::vector<FolderPair>::const_iterator pair = directoryPairsFormatted.begin(); pair != directoryPairsFormatted.end(); ++pair)
+ for (std::vector<FolderPairCfg>::const_iterator pair = directoryPairsFormatted.begin(); pair != directoryPairsFormatted.end(); ++pair)
{
- FolderCompareLine newEntry;
- newEntry.syncPair = *pair;
+ BaseDirMapping newEntry(pair->leftDirectory, pair->rightDirectory);
output.push_back(newEntry); //attention: push_back() copies by value!!! performance: append BEFORE writing values into fileCmp!
- FileComparison& fileCmp = output.back().fileCmp;
-
- //do basis scan: only result lines of type FILE_UNDEFINED (files that exist on both sides) need to be determined after this call
- this->performBaseComparison(*pair, fileCmp);
+ //do basis scan and retrieve candidates for binary comparison (files existing on both sides)
+ performBaseComparison(*pair, output.back(), compareCandidates);
}
//finish categorization...
+ std::vector<FileMapping*> filesToCompareBytewise;
- FolderCompRef rowsToCompareBytewise; //content comparison of file content happens AFTER finding corresponding files
+ //content comparison of file content happens AFTER finding corresponding files
//in order to separate into two processes (scanning and comparing)
- for (FolderComparison::iterator j = output.begin(); j != output.end(); ++j)
+ for (std::vector<FileMapping*>::iterator i = compareCandidates.begin(); i != compareCandidates.end(); ++i)
{
- FileComparison& fileCmp = j->fileCmp;
+ //pre-check: files have different content if they have a different filesize
+ if ((*i)->getFileSize<LEFT_SIDE>() != (*i)->getFileSize<RIGHT_SIDE>())
+ (*i)->cmpResult = FILE_DIFFERENT;
+ else
+ filesToCompareBytewise.push_back(*i);
+ }
- std::set<int> newEntry;
- for (FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i)
- {
- if (i->cmpResult == FILE_UNDEFINED)
- { //pre-check: files have different content if they have a different filesize
- if (i->fileDescrLeft.fileSize != i->fileDescrRight.fileSize)
- i->cmpResult = FILE_DIFFERENT;
- else
- newEntry.insert(i - fileCmp.begin());
- }
- }
- rowsToCompareBytewise.push_back(newEntry);
- }
- int objectsTotal = 0;
- wxULongLong dataTotal;
- getBytesToCompare(output, rowsToCompareBytewise, objectsTotal, dataTotal);
+ const int objectsTotal = filesToCompareBytewise.size() * 2;
+ const wxULongLong bytesTotal = getBytesToCompare(filesToCompareBytewise);
statusUpdater->initNewProcess(objectsTotal,
- globalFunctions::convertToSigned(dataTotal),
+ globalFunctions::convertToSigned(bytesTotal),
StatusHandler::PROCESS_COMPARING_CONTENT);
//compare files (that have same size) bytewise...
- for (FolderComparison::iterator j = output.begin(); j != output.end(); ++j)
+ for (std::vector<FileMapping*>::const_iterator j = filesToCompareBytewise.begin(); j != filesToCompareBytewise.end(); ++j)
{
- FileComparison& fileCmp = j->fileCmp;
+ FileMapping* const gridline = *j;
- //mark erroneous rows for deletion from output
- RemoveAtExit removeRowsAtExit(fileCmp); //note: running at individual folder pair level!
+ Zstring statusText = txtComparingContentOfFiles;
+ statusText.Replace(wxT("%x"), gridline->getRelativeName<LEFT_SIDE>(), false);
+ statusUpdater->updateStatusText(statusText);
- const std::set<int>& index = rowsToCompareBytewise[j - output.begin()];
- for (std::set<int>::const_iterator i = index.begin(); i != index.end(); ++i)
+ //check files that exist in left and right model but have different content
+ while (true)
{
- FileCompareLine& gridline = fileCmp[*i];
-
- Zstring statusText = txtComparingContentOfFiles;
- statusText.Replace(wxT("%x"), gridline.fileDescrLeft.relativeName.c_str(), false);
- statusUpdater->updateStatusText(statusText);
+ //trigger display refresh
+ statusUpdater->requestUiRefresh();
- //check files that exist in left and right model but have different content
- while (true)
+ try
{
- //trigger display refresh
- statusUpdater->requestUiRefresh();
+ if (filesHaveSameContentUpdating(gridline->getFullName<LEFT_SIDE>(),
+ gridline->getFullName<RIGHT_SIDE>(),
+ gridline->getFileSize<LEFT_SIDE>() * 2,
+ statusUpdater))
+ gridline->cmpResult = FILE_EQUAL;
+ else
+ gridline->cmpResult = FILE_DIFFERENT;
- try
+ statusUpdater->updateProcessedData(2, 0); //processed data is communicated in subfunctions!
+ break;
+ }
+ catch (FileError& error)
+ {
+ ErrorHandler::Response rv = statusUpdater->reportError(error.show());
+ if (rv == ErrorHandler::IGNORE_ERROR)
{
- if (filesHaveSameContentUpdating(gridline.fileDescrLeft.fullName,
- gridline.fileDescrRight.fullName,
- gridline.fileDescrLeft.fileSize * 2,
- statusUpdater))
- gridline.cmpResult = FILE_EQUAL;
- else
- gridline.cmpResult = FILE_DIFFERENT;
-
- statusUpdater->updateProcessedData(2, 0); //processed data is communicated in subfunctions!
+ gridline->cmpResult = FILE_CONFLICT; //same date, different filesize
+ gridline->conflictDescription = wxString(_("Conflict detected:")) + wxT("\n") + _("Comparing files by content failed.");
break;
}
- catch (FileError& error)
- {
- ErrorHandler::Response rv = statusUpdater->reportError(error.show());
- if (rv == ErrorHandler::IGNORE_ERROR)
- {
- removeRowsAtExit.markRow(*i);
- break;
- }
- else if (rv == ErrorHandler::RETRY)
- ; //continue with loop
- else
- assert (false);
- }
+
+ else if (rv == ErrorHandler::RETRY)
+ ; //continue with loop
+ else
+ assert (false);
}
}
}
}
-class ThreadSorting : public wxThread
+class MergeSides
{
public:
- ThreadSorting(DirectoryDescrType* directory) :
- wxThread(wxTHREAD_JOINABLE),
- m_directory(directory)
+ MergeSides(const Zstring& baseDirLeftPf,
+ const Zstring& baseDirRightPf,
+ std::vector<FileMapping*>& appendUndefinedOut) :
+ baseDirLeft(baseDirLeftPf),
+ baseDirRight(baseDirRightPf),
+ appendUndefined(appendUndefinedOut) {}
+
+ void execute(const DirContainer& leftSide, const DirContainer& rightSide, HierarchyObject& output)
{
- if (Create() != wxTHREAD_NO_ERROR)
- throw RuntimeException(wxString(wxT("Error creating thread for sorting!")));
- }
+ //ATTENTION: HierarchyObject::retrieveById() can only work correctly if the following conditions are fulfilled:
+ //1. on each level, files are added first, then directories (=> file id < dir id)
+ //2. when a directory is added, all subdirectories must be added immediately (recursion) before the next dir on this level is added
+ //3. entries may be deleted but NEVER new ones inserted!!!
+ //=> this allows for a quasi-binary search by id!
- ~ThreadSorting() {}
+ //reserve() fulfills two task here: 1. massive performance improvement! 2. ensure references in appendUndefined remain valid!
+ output.subFiles.reserve(leftSide.getSubFiles().size() + rightSide.getSubFiles().size()); //assume worst case!
+ output.subDirs.reserve( leftSide.getSubDirs().size() + rightSide.getSubDirs().size()); //
+ for (DirContainer::SubFileList::const_iterator i = leftSide.getSubFiles().begin(); i != leftSide.getSubFiles().end(); ++i)
+ {
+ DirContainer::SubFileList::const_iterator j = rightSide.getSubFiles().find(*i);
+
+ //find files that exist on left but not on right
+ if (j == rightSide.getSubFiles().end())
+ output.addSubFile(i->getData(), FILE_LEFT_SIDE_ONLY, FileMapping::nullData(),
+ RelNamesBuffered(baseDirLeft, //base sync dir postfixed
+ baseDirRight,
+ i->getParentRelNamePf())); //relative parent name postfixed
+ //find files that exist on left and right
+ else
+ {
+ appendUndefined.push_back(
+ &output.addSubFile(i->getData(), FILE_EQUAL, j->getData(), //FILE_EQUAL is just a dummy-value here
+ RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf())));
+ }
+ }
- ExitCode Entry()
- {
- std::sort(m_directory->begin(), m_directory->end());
- return 0;
- }
+ //find files that exist on right but not on left
+ for (DirContainer::SubFileList::const_iterator j = rightSide.getSubFiles().begin(); j != rightSide.getSubFiles().end(); ++j)
+ {
+ if (leftSide.getSubFiles().find(*j) == leftSide.getSubFiles().end())
+ output.addSubFile(FileMapping::nullData(), FILE_RIGHT_SIDE_ONLY, j->getData(),
+ RelNamesBuffered(baseDirLeft, baseDirRight, j->getParentRelNamePf()));
+ }
-private:
- DirectoryDescrType* m_directory;
-};
+//-----------------------------------------------------------------------------------------------
+ for (DirContainer::SubDirList::const_iterator i = leftSide.getSubDirs().begin(); i != leftSide.getSubDirs().end(); ++i)
+ {
+ DirContainer::SubDirList::const_iterator j = rightSide.getSubDirs().find(*i);
-void CompareProcess::performBaseComparison(const FolderPair& pair, FileComparison& output)
-{
- //PERF_START;
- //retrieve sets of files (with description data)
- DirectoryDescrType* directoryLeft = descriptionBuffer->getDirectoryDescription(pair.leftDirectory);
- DirectoryDescrType* directoryRight = descriptionBuffer->getDirectoryDescription(pair.rightDirectory);
+ //find directories that exist on left but not on right
+ if (j == rightSide.getSubDirs().end())
+ {
+ DirMapping& newDirMap = output.addSubDir(i->getData(), DIR_LEFT_SIDE_ONLY, DirMapping::nullData(),
+ RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf()));
+ fillOneSide<true>(*i, newDirMap); //recurse into subdirectories
+ }
+ else //directories that exist on both sides
+ {
+ DirMapping& newDirMap = output.addSubDir(i->getData(), DIR_EQUAL, j->getData(),
+ RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf()));
+ execute(*i, *j, newDirMap); //recurse into subdirectories
+ }
+ }
- statusUpdater->updateStatusText(_("Generating file list..."));
- statusUpdater->forceUiRefresh(); //keep total number of scanned files up to date
- //PERF_STOP;
+ //find directories that exist on right but not on left
+ for (DirContainer::SubDirList::const_iterator j = rightSide.getSubDirs().begin(); j != rightSide.getSubDirs().end(); ++j)
+ {
+ if (leftSide.getSubDirs().find(*j) == leftSide.getSubDirs().end())
+ {
+ DirMapping& newDirMap = output.addSubDir(DirMapping::nullData(), DIR_RIGHT_SIDE_ONLY, j->getData(),
+ RelNamesBuffered(baseDirLeft, baseDirRight, j->getParentRelNamePf()));
+ fillOneSide<false>(*j, newDirMap); //recurse into subdirectories
+ }
+ }
+ }
- //we use binary search when comparing the directory structures: so sort() first
- if (wxThread::GetCPUCount() >= 2) //do it the multithreaded way:
+private:
+ template <bool leftSide>
+ void fillOneSide(const DirContainer& dirCont, HierarchyObject& output)
{
- //no synchronization (multithreading) needed here: directoryLeft and directoryRight are disjunct
- //reference counting Zstring also shouldn't be an issue, as no strings are deleted during std::sort()
- std::auto_ptr<ThreadSorting> sortLeft(new ThreadSorting(directoryLeft));
- std::auto_ptr<ThreadSorting> sortRight(new ThreadSorting(directoryRight));
+ //reserve() fulfills two task here: 1. massive performance improvement! 2. ensure references in appendUndefined remain valid!
+ output.subFiles.reserve(dirCont.getSubFiles().size());
+ output.subDirs.reserve( dirCont.getSubDirs(). size());
- if (sortLeft->Run() != wxTHREAD_NO_ERROR)
- throw RuntimeException(wxString(wxT("Error starting thread for sorting!")));
+ for (DirContainer::SubFileList::const_iterator i = dirCont.getSubFiles().begin(); i != dirCont.getSubFiles().end(); ++i)
+ {
+ if (leftSide)
+ output.addSubFile(i->getData(), FILE_LEFT_SIDE_ONLY, FileMapping::nullData(),
+ RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf()));
+ else
+ output.addSubFile(FileMapping::nullData(), FILE_RIGHT_SIDE_ONLY, i->getData(),
+ RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf()));
+ }
- if (directoryLeft != directoryRight) //attention: might point to the same vector because of buffer!
+ for (DirContainer::SubDirList::const_iterator i = dirCont.getSubDirs().begin(); i != dirCont.getSubDirs().end(); ++i)
{
- if (sortRight->Run() != wxTHREAD_NO_ERROR)
- throw RuntimeException(wxString(wxT("Error starting thread for sorting!")));
+ DirMapping& newDirMap = leftSide ?
+ output.addSubDir(i->getData(), DIR_LEFT_SIDE_ONLY, DirMapping::nullData(),
+ RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf())) :
+ output.addSubDir(DirMapping::nullData(), DIR_RIGHT_SIDE_ONLY, i->getData(),
+ RelNamesBuffered(baseDirLeft, baseDirRight, i->getParentRelNamePf()));
- if (sortRight->Wait() != 0)
- throw RuntimeException(wxString(wxT("Error waiting for thread (sorting)!")));
+ fillOneSide<leftSide>(*i, newDirMap); //recurse into subdirectories
}
-
- if (sortLeft->Wait() != 0)
- throw RuntimeException(wxString(wxT("Error waiting for thread (sorting)!")));
}
- else //single threaded
- {
- std::sort(directoryLeft->begin(), directoryLeft->end());
- if (directoryLeft != directoryRight) //attention: might point to the same vector because of buffer!
- std::sort(directoryRight->begin(), directoryRight->end());
- }
- //PERF_STOP;
- //reserve some space to avoid too many vector reallocations: doesn't make much sense for multiple folder pairs, but doesn't hurt either
- output.reserve(output.size() + unsigned(std::max(directoryLeft->size(), directoryRight->size()) * 1.2));
+ const Zstring& baseDirLeft;
+ const Zstring& baseDirRight;
+ std::vector<FileMapping*>& appendUndefined;
+};
- //begin base comparison
- FileCompareLine newline(FILE_UNDEFINED, SYNC_DIR_NONE, true);
- for (DirectoryDescrType::const_iterator i = directoryLeft->begin(); i != directoryLeft->end(); ++i)
- {
- //find files/folders that exist in left file tree but not in right one
- DirectoryDescrType::const_iterator j = custom_binary_search(directoryRight->begin(), directoryRight->end(), *i);
- if (j == directoryRight->end())
- {
- newline.fileDescrLeft = *i;
- newline.fileDescrRight = FileDescrLine();
- newline.cmpResult = FILE_LEFT_SIDE_ONLY;
- output.push_back(newline);
- }
- //find files/folders that exist on left and right side
- else
- {
- const FileDescrLine::ObjectType typeLeft = i->objType;
- const FileDescrLine::ObjectType typeRight = j->objType;
- //files...
- if (typeLeft == FileDescrLine::TYPE_FILE && typeRight == FileDescrLine::TYPE_FILE)
- {
- newline.fileDescrLeft = *i;
- newline.fileDescrRight = *j;
- newline.cmpResult = FILE_UNDEFINED; //not yet determined!
- output.push_back(newline);
- }
- //directories...
- else if (typeLeft == FileDescrLine::TYPE_DIRECTORY && typeRight == 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
- {
- assert (typeLeft != typeRight);
+void CompareProcess::performBaseComparison(const FolderPairCfg& pair, BaseDirMapping& output, std::vector<FileMapping*>& appendUndefined)
+{
+ assert(output.subDirs.empty());
+ assert(output.subFiles.empty());
- newline.fileDescrLeft = *i;
- newline.fileDescrRight = FileDescrLine();
- newline.cmpResult = FILE_LEFT_SIDE_ONLY;
- output.push_back(newline);
+ //PERF_START;
- newline.fileDescrLeft = FileDescrLine();
- newline.fileDescrRight = *j;
- newline.cmpResult = FILE_RIGHT_SIDE_ONLY;
- output.push_back(newline);
- }
- }
- }
+ //scan directories
+ const DirContainer& directoryLeft = directoryBuffer->getDirectoryDescription(pair.leftDirectory,
+ pair.filterIsActive,
+ pair.includeFilter,
+ pair.excludeFilter);
+ const DirContainer& directoryRight = directoryBuffer->getDirectoryDescription(pair.rightDirectory,
+ pair.filterIsActive,
+ pair.includeFilter,
+ pair.excludeFilter);
- for (DirectoryDescrType::const_iterator j = directoryRight->begin(); j != directoryRight->end(); ++j)
- {
- //find files/folders that exist in right file model but not in left model
- if (custom_binary_search(directoryLeft->begin(), directoryLeft->end(), *j) == directoryLeft->end())
- {
- newline.fileDescrLeft = FileDescrLine();
- newline.fileDescrRight = *j;
- newline.cmpResult = FILE_RIGHT_SIDE_ONLY;
- output.push_back(newline);
- }
- }
- //PERF_STOP
+ statusUpdater->updateStatusText(_("Generating file list..."));
+ statusUpdater->forceUiRefresh(); //keep total number of scanned files up to date
+
+ //PERF_STOP;
+
+ MergeSides(pair.leftDirectory,
+ pair.rightDirectory,
+ appendUndefined).execute(directoryLeft, directoryRight, output);
+ //PERF_STOP;
}
bgstack15