changeset 0:a3834af36579

Working with memory backend.
author Tom Fredrik Blenning Klaussen <bfg@blenning.no>
date Mon, 20 Aug 2012 15:49:48 +0200
parents
children aae83c0a771d
files CMakeLists.txt DataController.cpp DataController.hpp EditDistance.cpp EditDistance.hpp Exception.hpp FileDbLink.cpp FileDbLink.hpp IOException.cpp IOException.hpp MemoryDbLink.cpp MemoryDbLink.hpp OrderedPair.hpp PermissionException.hpp PrecompiledHeader.cmake ProtectedContainer.hpp ProtectedDictionary.hpp TestEditDistance.cpp TestFramework.hpp ThreadSafeLookup.hpp main.cpp
diffstat 21 files changed, 1165 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CMakeLists.txt	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,47 @@
+PROJECT(DeDupe)
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6.4)
+
+INCLUDE(PrecompiledHeader.cmake)
+
+FIND_PACKAGE(Boost)
+IF (Boost_FOUND)
+    INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR})
+    ADD_DEFINITIONS( "-DHAS_BOOST" )
+ENDIF()
+
+
+FIND_PACKAGE(Qt4 COMPONENTS QtOpenGL QtXml REQUIRED)
+
+INCLUDE(${QT_USE_FILE})
+ADD_DEFINITIONS(${QT_DEFINITIONS})
+
+
+SET(SOURCES
+	DataController.cpp
+	EditDistance.cpp
+	IOException.cpp
+	FileDbLink.cpp
+	MemoryDbLink.cpp
+	main.cpp
+)
+
+SET(MOC_HEADERS
+	DataController.hpp
+)
+
+# Returns the moc_xxx.cpp files in the foo_MOC_SRCS variable
+QT4_WRAP_CPP(MOC_SOURCES ${MOC_HEADERS})
+
+MESSAGE(WARNING ${MOC_SOURCES})
+
+SET(CMAKE_CXX_FLAGS "-g2 -Wall -Werror -fno-inline")
+ADD_EXECUTABLE(DeDupe ${SOURCES} ${MOC_SOURCES})
+TARGET_LINK_LIBRARIES(DeDupe ${QT_LIBRARIES})
+
+ENABLE_TESTING()
+
+ADD_EXECUTABLE(TestEditDistance TestEditDistance.cpp EditDistance.cpp)
+ADD_TEST(TestEditDistance TestEditDistance)
+
+TARGET_LINK_LIBRARIES(TestEditDistance ${QT_LIBRARIES})
+#ADD_PRECOMPILED_HEADER(TestEditDistance TestFramework.hpp)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DataController.cpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,326 @@
+#include "DataController.hpp"
+
+#include "MemoryDbLink.hpp"
+
+#include "PermissionException.hpp"
+#include "DataController.hpp"
+
+#include <QtGui/QApplication>
+#include <QtCore/QDir>
+
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+#include <QtCore/QCryptographicHash>
+#include <QtCore/QDateTime>
+
+#include <QtGui/QMainWindow>
+#include <QtGui/QTreeWidget>
+#include <QtGui/QHeaderView>
+#include <QtGui/QMenuBar>
+#include <QtGui/QToolBar>
+#include <QtGui/QDoubleSpinBox>
+#include <QtGui/QLabel>
+#include <QtGui/QHBoxLayout>
+#include <QtGui/QProgressBar>
+
+#include "EditDistance.hpp"
+
+#include <cassert>
+void findFiles(const QDir& dir, FileDbLink& dblink)
+{
+  /*
+  QProgressDialog progressDialog(this);
+  progressDialog.setCancelButtonText(tr("&Cancel"));
+  progressDialog.setRange(0, files.size());
+  progressDialog.setWindowTitle(tr("Find Files"));
+  */
+
+  foreach(QString filename, dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs)) {
+    filename = dir.absoluteFilePath(filename);
+    findFiles(QDir(filename), dblink);
+  }
+
+  foreach(QString filename, dir.entryList(QDir::Files)) {
+    filename = dir.absoluteFilePath(filename);
+    try {
+      dblink.updateIfModified(filename);
+    }
+    catch (const PermissionException& e) {
+      qDebug()<<e.toString();
+    }
+    catch (Exception& e) {
+      qDebug()<<e.toString();
+      exit(1);
+    }
+  }
+}
+
+QTreeWidgetItem* DataController::createItem(const FileDbLink::DBInfo& info)
+{
+  QTreeWidgetItem* item = new QTreeWidgetItem();
+  item->setData(0, Qt::DisplayRole, info.name());
+  item->setData(0, 32, info.path());
+  item->setData(0, 33, info.name());
+  
+  item->setData(1, Qt::DisplayRole, info.size());
+  item->setData(2, Qt::DisplayRole, info.mtime());
+  item->setData(3, Qt::DisplayRole, info.checksum().toHex());
+  return item;
+}
+
+void DataController::delayPopulate()
+{
+  populateDelay->start();
+}
+
+
+void DataController::populate()
+{
+  populate(nameFilter->isChecked(), sizeFilter->isChecked(), mtimeFilter->isChecked(),
+	   checksumFilter->isChecked(), (100 - editCutoffSpin->value()) * .01);
+}
+
+void DataController::populate(bool showNameDups, bool showSizeDups,
+			      bool showMTimeDups, bool showCheckSumDups,
+			      float editDistanceCutoff)
+{
+  tw->clear();
+
+  const QList<QSharedPointer<FileDbLink::DBInfo> > elems = dblink->sortOn(FileDbLink::EDIT, true);
+
+  QProgressBar bar;
+
+  bar.resize(200,25);
+  bar.setValue(0);
+  bar.setMinimum(0);
+  bar.setMaximum(elems.size());
+  bar.show();
+
+  connect(this, SIGNAL(populateProgress(int)), &bar, SLOT(setValue(int)));
+
+  int n = 0;
+
+  tw->setUpdatesEnabled(false);
+
+  foreach(QSharedPointer<FileDbLink::DBInfo> line, elems) {
+    QTreeWidgetItem* item = 0;
+    bool anyAdded = false;
+
+    if (showNameDups) {
+      QTreeWidgetItem* topLevelItem = 0;
+      foreach(QSharedPointer<FileDbLink::DBInfo> dup, elems) {
+	if(dup != line && line->name() == dup->name() ) {
+	  if (!topLevelItem) {
+	    topLevelItem =  new QTreeWidgetItem();
+	    topLevelItem->setData(0, Qt::DisplayRole, "Name");
+	    if(!item)
+	      item = createItem(*line);
+	    item->addChild(topLevelItem);
+	    anyAdded = true;
+	  }
+	  topLevelItem->addChild(createItem(*dup));
+	}
+      }
+    }
+
+    if (showSizeDups) {
+      QTreeWidgetItem* topLevelItem = 0;
+      foreach(QSharedPointer<FileDbLink::DBInfo> dup, elems) {
+	if(dup != line && line->size() == dup->size() ) {
+	  if (!topLevelItem) {
+	    topLevelItem =  new QTreeWidgetItem();
+	    topLevelItem->setData(0, Qt::DisplayRole, "Size");
+	    if(!item)
+	      item = createItem(*line);
+	    item->addChild(topLevelItem);
+	    anyAdded = true;
+	  }
+	  topLevelItem->addChild(createItem(*dup));
+	}
+      }
+    }
+
+    if (showMTimeDups) {
+      QTreeWidgetItem* topLevelItem = 0;
+      foreach(QSharedPointer<FileDbLink::DBInfo> dup, elems) {
+	if(dup != line && line->mtime() == dup->mtime() ) {
+	  if (!topLevelItem) {
+	    topLevelItem =  new QTreeWidgetItem();
+	    topLevelItem->setData(0, Qt::DisplayRole, "MTime");
+	    if(!item)
+	      item = createItem(*line);
+	    item->addChild(topLevelItem);
+	    anyAdded = true;
+	  }
+	  topLevelItem->addChild(createItem(*dup));
+	}
+      }
+    }
+
+    if (showCheckSumDups) {
+      QTreeWidgetItem* topLevelItem = 0;
+      foreach(QSharedPointer<FileDbLink::DBInfo> dup, elems) {
+	if(dup != line && line->checksum() == dup->checksum() ) {
+	  if (!topLevelItem) {
+	    topLevelItem =  new QTreeWidgetItem();
+	    topLevelItem->setData(0, Qt::DisplayRole, "Checksum");
+	    if(!item)
+	      item = createItem(*line);
+	    item->addChild(topLevelItem);
+	    anyAdded = true;
+	  }
+	  topLevelItem->addChild(createItem(*dup));
+	}
+      }
+    }
+
+    if (editDistanceCutoff < 1.0) {
+      QTreeWidgetItem* topLevelItem = 0;
+      QMultiMap<int, QSharedPointer<FileDbLink::DBInfo> > oList;
+
+      int absoluteCutoff = line->name().length() * editDistanceCutoff;
+      foreach(QSharedPointer<FileDbLink::DBInfo> dup, elems) {
+	if(dup != line) {
+	  int distance = EditDistance::Compute(line->name(), dup->name());
+
+	  if (distance <= absoluteCutoff) {
+	    oList.insert(distance, dup);
+	  }
+	}
+      }
+
+      if (oList.size() > 0 ) {
+	topLevelItem =  new QTreeWidgetItem();
+	topLevelItem->setData(0, Qt::DisplayRole, "Editdistance");
+	if(!item)
+	  item = createItem(*line);
+	item->addChild(topLevelItem);
+	anyAdded = true;
+      }
+      
+      foreach(QSharedPointer<FileDbLink::DBInfo> dup, oList.values()) {
+	topLevelItem->addChild(createItem(*dup));
+      }
+    }
+
+
+
+    if (item)
+      tw->addTopLevelItem(item);
+    emit populateProgress(++n);
+    if (n % 64 == 0) {
+      QCoreApplication::processEvents();
+    }
+  }
+  tw->setUpdatesEnabled(true);
+
+}
+
+
+DataController::DataController() : showFullPath(false)
+{
+  populateDelay = new QTimer(this);
+  populateDelay->setSingleShot(true);
+  populateDelay->setInterval(500);
+  connect(populateDelay, SIGNAL(timeout()), this, SLOT(populate()));
+
+  dblink = new MemoryDbLink();
+
+  findFiles(QDir("."), *dblink);
+
+  mw = new QMainWindow();
+  QMenuBar* mb = new QMenuBar();
+
+  QMenu* menu = mb->addMenu("&View");
+  QAction* action = menu->addAction("Show full path");
+  action->setCheckable(true);
+  connect(action, SIGNAL(toggled(bool)), this, SLOT(setShowFullPath(bool)));
+
+  mw->setMenuBar(mb);
+
+  QToolBar* filterBar = new QToolBar("Filters");
+
+  nameFilter = filterBar->addAction("Name");
+  nameFilter->setCheckable(true);
+  connect(nameFilter, SIGNAL(toggled(bool)), this, SLOT(delayPopulate()));
+
+  sizeFilter = filterBar->addAction("Size");
+  sizeFilter->setCheckable(true);
+  connect(sizeFilter, SIGNAL(toggled(bool)), this, SLOT(delayPopulate()));
+
+  mtimeFilter = filterBar->addAction("MTime");
+  mtimeFilter->setCheckable(true);
+  connect(mtimeFilter, SIGNAL(toggled(bool)), this, SLOT(delayPopulate()));
+
+  checksumFilter = filterBar->addAction("Checksum");
+  checksumFilter->setCheckable(true);
+  connect(checksumFilter, SIGNAL(toggled(bool)), this, SLOT(delayPopulate()));
+
+  QWidget* widget = new QWidget();
+  QLayout* layout = new QHBoxLayout();
+  layout->setContentsMargins(0,0,0,0);
+  widget->setLayout(layout);
+  layout->addWidget(new QLabel("Edit distance", widget));
+  editCutoffSpin = new QSpinBox(widget);
+  layout->addWidget(editCutoffSpin);
+  editCutoffSpin->setMinimum(0);
+  editCutoffSpin->setMaximum(100);
+  editCutoffSpin->setValue(70);
+  editCutoffSpin->setSingleStep(10);
+  editCutoffSpin->setSuffix("%");
+  connect(editCutoffSpin, SIGNAL(valueChanged(int)), this, SLOT(delayPopulate()));
+  filterBar->addWidget(widget);
+
+  mw->addToolBar(filterBar);
+
+  tw = new QTreeWidget(mw);
+  mw->setCentralWidget(tw);
+  tw->setEditTriggers(QAbstractItemView::NoEditTriggers);
+
+  tw->setHeaderLabels(QString("Path;Size;Date;Checksum").split(";"));	
+
+  populate();
+
+  tw->setSortingEnabled(true);
+  tw->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+  tw->setSelectionBehavior(QAbstractItemView::SelectRows);
+  //tw->resizeColumnsToContents();
+  mw->resize(800,800);
+  mw->show();
+
+
+}
+
+DataController::~DataController()
+{
+}
+
+
+void DataController::cellDoubleClicked(int row, int column)
+{
+  if(column == 0) {
+    toggleShowFullPath();
+  }
+}
+
+
+void DataController::setShowFullPath(bool showFullPath)
+{
+  this->showFullPath = showFullPath;
+
+  tw->setSortingEnabled(false);
+
+  int role = (showFullPath)?32:33;
+  for (int row = 0; row < tw->topLevelItemCount(); ++row) {
+    QTreeWidgetItem* pathItem = tw->topLevelItem(row);
+    pathItem->setData(0, Qt::DisplayRole, pathItem->data(0,role));
+  }
+  tw->setSortingEnabled(true);
+}
+
+bool DataController::toggleShowFullPath()
+{
+  bool showFullPath = ! this->showFullPath;
+  setShowFullPath(showFullPath);
+  return showFullPath;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DataController.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,57 @@
+#ifndef DATACONTROLLER_HPP
+#define DATACONTROLLER_HPP
+
+#include <QtCore/QObject>
+
+#include "FileDbLink.hpp"
+
+class QMainWindow;
+class QTreeWidget;
+class QTreeWidgetItem;
+class QAction;
+class QSpinBox;
+class QTimer;
+
+class DataController : QObject {
+private:
+  Q_OBJECT
+
+  public:
+  DataController();
+  ~DataController();
+
+public slots:
+  //void cellClicked(int row, int column);
+  void cellDoubleClicked(int row, int column);
+  bool toggleShowFullPath();
+  void setShowFullPath(bool);
+  void populate();
+  void delayPopulate();
+
+
+signals:
+  void populateProgress(int);
+
+private:
+  void populate(bool showNameDups, bool showSizeDups,
+		bool showMTimeDups, bool showCheckSumDups,
+		float editDistanceCutoff);
+
+  QTreeWidgetItem* createItem(const FileDbLink::DBInfo& info);
+
+  bool showFullPath;
+  QMainWindow* mw;
+  QTreeWidget* tw;
+
+  FileDbLink* dblink;
+
+  QAction* nameFilter;
+  QAction* sizeFilter;
+  QAction* mtimeFilter;
+  QAction* checksumFilter;
+  QSpinBox* editCutoffSpin;
+
+  QTimer* populateDelay;
+};
+
+#endif //DATACONTROLLER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/EditDistance.cpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,80 @@
+#include "EditDistance.hpp"
+
+#include <QtCore/QList>
+
+#define CharComparer(A, B) (QChar(A) == QChar(B))
+
+EditDistance::cacheType EditDistance::cache;
+
+QString EditDistance::removeDiacritics(QString in)
+{
+  QString out;
+  foreach(QChar c, in) {
+    if (c.decompositionTag() == QChar::NoDecomposition) {
+      out.append(c);
+    }
+    else {
+      QString tmp = c.decomposition();
+      out.append(tmp[0]);
+    }
+  }
+  return out;
+}
+
+int EditDistance::Compute(QString a, QString b, bool remove) {
+  if (remove) {
+    a=removeDiacritics(a);
+    b=removeDiacritics(b);
+  }
+
+  OrderedPair<QString> lup(a, b);
+
+  boost::optional<int> res = cache.value(lup);
+  if (res)
+    return *res;
+
+  
+  // Allocate distance matrix
+  QList<QList<int> > d;
+  QList<int> temp;
+  for (int i=0;i<b.size()+1;i++) {
+    temp.append(0);
+  }
+  for (int i=0;i<a.size()+1;i++) {
+    d.append(temp);
+  }
+#if 0
+  // Get character comparer
+  CharComparer isEqual = (ignoreCase) ?
+    (CharComparer)CharCompareIgnoreCase : CharCompare;
+#endif
+  // Compute distance
+  for (int i = 0; i <= a.size(); i++)
+    d[i][0] = i;
+  for (int j = 0; j <= b.size(); j++)
+    d[0][j] = j;
+  for (int i = 1; i <= a.size(); i++)
+    {
+      for (int j = 1; j <= b.size(); j++)
+	{
+	  if (CharComparer(a.at(i-1), b.at(j - 1)))
+	    {
+	      // No change required
+	      d[i][j] = d[i - 1][j - 1];
+	    }
+	  else
+	    {
+	      d[i][ j] =
+		std::min(d[i - 1][ j] + 1,    // Deletion
+			 std::min(d[i][ j - 1] + 1,    // Insertion
+				  d[i - 1][ j - 1] + 1));       // Substitution
+	    }
+	}
+    }
+  
+  // Return final value
+  int retVal = d[a.size()][ b.size()];
+  cache.insert(lup, retVal);
+
+  return retVal;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/EditDistance.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,24 @@
+#ifndef EDITDISTANCE_HPP
+#define EDITDISTANCE_HPP
+
+#include "OrderedPair.hpp"
+
+#include <QtCore/QString>
+#include <QtCore/QMap>
+#include <QtCore/QHash>
+
+#include "ThreadSafeLookup.hpp"
+
+class EditDistance {
+protected:
+  typedef ThreadSafeLookup<OrderedPair<QString>, int> cacheType;
+  //typedef QMap<OrderedPair<QString>, int> cacheType;
+  //typedef QHash<OrderedPair<QString>, int> cacheType;
+public:
+  static int Compute(QString a, QString b, bool removeDiacritics = false);
+  static QString removeDiacritics(QString in);
+
+  static cacheType cache; 
+};
+
+#endif //EDITDISTANCE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Exception.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,26 @@
+#ifndef EXCEPTION_HPP
+#define EXCEPTION_HPP
+
+#include <QtCore/QString>
+
+class Exception {
+
+public:
+  virtual QString toString() const
+  {
+    return errorMsg_;
+  }
+
+
+protected:
+  Exception(const QString& errorMsg) : errorMsg_(errorMsg) {}
+  virtual ~Exception() {}
+
+  void setErrorMsg(QString& errorMsg);
+  const QString& errorMsg(QString& errorMsg);
+
+private:
+  QString errorMsg_;
+};
+
+#endif //EXCEPTION_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/FileDbLink.cpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,140 @@
+#include "FileDbLink.hpp"
+
+#include <QtCore/QDebug>
+#include <QtCore/QtConcurrentMap>
+
+#include "PermissionException.hpp"
+#include "EditDistance.hpp"
+
+#include <cassert>
+
+#include <functional> 
+#include <algorithm> 
+#include <iostream>
+
+#include <boost/bind.hpp>
+
+void FileDbLink::updateIfModified(const QString& path)
+{
+  QFileInfo fileinfo(path);
+  if (!exists(path)) {
+    addFile(fileinfo);
+  }
+}
+
+void FileDbLink::addFile(const QFileInfo& fileinfo)
+{
+  addFile(fileinfo.absoluteFilePath(), fileinfo.size(), fileinfo.lastModified());
+}
+
+void FileDbLink::addFile(const QString& path, qint64 size, const QDateTime& lastModified)
+{
+  QCryptographicHash hash( QCryptographicHash::Sha1 );
+  QFile file(path);
+  if ( file.open( QIODevice::ReadOnly ) ) {
+    hash.addData( file.readAll() );
+  }
+  else {
+    QString errorMsg = path + ": " + file.errorString();
+    qDebug()<<file.error();
+    switch (file.error()) {
+    case QFile::PermissionsError:
+      throw PermissionException(errorMsg);
+    default:
+      throw IOException(errorMsg);
+    }
+  }
+  
+  addFile(path, size, lastModified, hash);
+
+}
+
+const QList<QSharedPointer<FileDbLink::DBInfo> > FileDbLink::sortOn(SORTORDER order, bool extended)
+{
+  QList<QSharedPointer<DBInfo> > list = (extended) ? computedValues() : computedValues();
+
+  switch (order) {
+  case PATH:
+    {
+      QList<QSharedPointer<FileDbLink::DBInfo> > oList;
+      foreach(QSharedPointer<DBInfo> info, list) {
+	oList.push_back(info);
+      }
+      return oList;
+    }
+  case SIZE:
+    {
+      QMultiMap<quint64, QSharedPointer<DBInfo> > oList;
+      foreach(QSharedPointer<DBInfo> info, list) {
+	oList.insert(info->size(), info);
+      }
+      return oList.values();
+    }
+  case MTIME:
+    {
+      QMultiMap<QDateTime, QSharedPointer<DBInfo> > oList;
+      foreach(QSharedPointer<DBInfo> info, list) {
+	oList.insert(info->mtime(), info);
+      }
+      return oList.values();
+    }
+  case CHECKSUM:
+    {
+      QMultiMap<QByteArray, QSharedPointer<DBInfo> > oList;
+      foreach(QSharedPointer<DBInfo> info, list) {
+	oList.insert(info->checksum(), info);
+      }
+      return oList.values();
+    }
+  case EDIT:
+    {
+      assert(extended);
+      QMultiMap<int, QSharedPointer<DBInfo> > oList;
+      foreach(QSharedPointer<DBInfo> info, list) {
+	QSharedPointer<ExtendedDBInfo> ptr;
+	ptr = info.dynamicCast<ExtendedDBInfo>();
+	oList.insert(ptr->editDistance(), info);
+      }
+      return oList.values();
+      
+    }
+  }
+  abort();
+}
+
+QSharedPointer<FileDbLink::DBInfo> FileDbLink::computedValue(const QSharedPointer<DBInfo>& info,
+							     const QList<QSharedPointer<DBInfo> >& entries)
+{
+  QString p1 = info->name();
+  int minDist = 100000;
+  QString other;
+  for (QList<QSharedPointer<DBInfo> >::const_iterator it2 = entries.begin();
+       it2 != entries.end(); ++it2) {
+    if (info == *it2)
+      continue;
+    QString p2 = (*it2)->name();
+    int dist = EditDistance::Compute(p1, p2, false);
+    if (dist < minDist) {
+      minDist = dist;
+      other = (*it2)->path();
+    }
+  }
+  return QSharedPointer<DBInfo>(new ExtendedDBInfo(*info, other, minDist));
+}
+
+const QList<QSharedPointer<FileDbLink::DBInfo> > FileDbLink::computedValues() const
+{
+  QList<QSharedPointer<DBInfo> > list;
+  QList<QSharedPointer<DBInfo> > entries = values();
+
+#if 1
+  list = QtConcurrent::blockingMapped(entries, boost::bind( &FileDbLink::computedValue, _1, entries));
+#else
+  for (QList<QSharedPointer<DBInfo> >::const_iterator it1 = entries.begin();
+       it1 != entries.end(); ++it1) {
+    QSharedPointer<DBInfo> ext = computedValue(*it1, entries);
+    list.push_back(ext);
+  }
+#endif
+  return list;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/FileDbLink.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,109 @@
+#ifndef FILEDBLINK
+#define FILEDBLINK
+
+#include <QtCore/QDateTime>
+#include <QtCore/QCryptographicHash>
+#include <QtCore/QFileInfo>
+
+class FileDbLink {
+public:
+  class DBInfo {
+  public:
+    DBInfo(const QString& path, qint64 size, const QDateTime& mtime, const QByteArray& hash) : path_(path),
+												       size_(size),
+												       mtime_(mtime),
+												       hash_(hash)
+    {}
+    DBInfo() {}
+
+    virtual ~DBInfo() {}
+
+    const QString& path() const
+    {
+      return path_;
+    }
+
+    QString name() const
+    {
+      QFileInfo finf(path());
+      return finf.fileName();
+    }
+
+    quint64 size() const
+    {
+      return size_;
+    }
+
+    const QDateTime& mtime() const
+    {
+      return mtime_;
+    }
+
+    const QByteArray checksum() const
+    {
+      return hash_;
+    }
+
+    virtual QString serialize() const
+    {
+      QString size = QString::number(size_);
+      QString str = path_;
+      str += ", " + size;
+      str += ", " + mtime_.toString();
+      str += ", " + hash_.toHex();
+      return str;
+    }
+
+  private:
+    QString path_;
+    qint64 size_;
+    QDateTime mtime_;
+    QByteArray hash_;
+  };
+
+  class ExtendedDBInfo : public DBInfo {
+  public:
+    ExtendedDBInfo(const DBInfo& dbinfo, const QString& closestEditPath, int editDistance) : DBInfo(dbinfo), closestEditPath_(closestEditPath), editDistance_(editDistance) {}
+    ExtendedDBInfo() {}
+
+    virtual ~ExtendedDBInfo() {}
+
+    virtual QString serialize() const
+    {
+      QString dist = QString::number(editDistance_);
+      QString str = DBInfo::serialize();
+      str += ", " + closestEditPath_;
+      str += ", " + dist;
+      return str;
+    }
+
+    int editDistance() const
+    {
+      return editDistance_;
+    }
+
+  private:
+    QString closestEditPath_;
+    int editDistance_;
+  };
+
+  static QSharedPointer<DBInfo> computedValue(const QSharedPointer<DBInfo>& info, const QList<QSharedPointer<DBInfo> >&);
+
+
+public:
+  virtual ~FileDbLink() {}
+
+  void updateIfModified(const QString& path);
+  virtual void addFile(const QString& path, qint64 size, const QDateTime& dtime, const QCryptographicHash& hash) = 0;
+  void addFile(const QString& path, qint64 size, const QDateTime& dtime);
+  void addFile(const QFileInfo& fileinfo);
+  virtual bool exists(const QString& path) = 0;
+  virtual const QList<QSharedPointer<DBInfo> > values() const = 0;
+  virtual const QList<QSharedPointer<DBInfo> > computedValues() const;
+
+  enum SORTORDER { PATH, SIZE, MTIME, CHECKSUM, EDIT };
+
+  virtual const QList<QSharedPointer<DBInfo> > sortOn(SORTORDER order, bool extended = false);
+};
+
+#endif //FILEDBLINK
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/IOException.cpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,5 @@
+#include "IOException.hpp"
+
+IOException::IOException(const QString& errorMsg) : Exception(errorMsg)
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/IOException.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,11 @@
+#ifndef IOEXCEPTION_HPP
+#define IOEXCEPTION_HPP
+
+#include "Exception.hpp"
+
+class IOException : public Exception {
+public:
+  IOException(const QString& errMsg);
+};
+
+#endif //IOEXCEPTION_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MemoryDbLink.cpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,41 @@
+#include "MemoryDbLink.hpp"
+
+#include <QtCore/QStringList>
+
+void MemoryDbLink::addFile(const QString& path, qint64 size, const QDateTime& dtime, const QCryptographicHash& hash)
+{
+  addFile(DBInfo(path, size, dtime, hash.result()));
+}
+
+bool MemoryDbLink::tryAddFile(const DBInfo& dbinfo)
+{
+  QMap<QString, QSharedPointer<DBInfo> >::iterator pos;
+  pos = entries.find(dbinfo.path());
+  if (pos == entries.end()) {
+    entries.insert(dbinfo.path(), QSharedPointer<DBInfo>(new DBInfo(dbinfo)));
+    return true;
+  }
+  return false;
+}
+
+void MemoryDbLink::addFile(const DBInfo& dbinfo)
+{
+  if (!tryAddFile(dbinfo)) {
+    abort(); //Should throw exception
+  }
+}
+
+
+QStringList MemoryDbLink::toStringList()
+{
+  QStringList list;
+  foreach(QSharedPointer<DBInfo> info, entries) {
+    list << info->serialize();
+  }
+  return list;
+}
+
+const QList<QSharedPointer<FileDbLink::DBInfo> > MemoryDbLink::values() const
+{
+  return entries.values();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MemoryDbLink.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,25 @@
+#ifndef MEMORYDBLINK_HPP
+#define MEMORYDBLINK_HPP
+#include "FileDbLink.hpp"
+
+#include <QtCore/QMap>
+#include <QtCore/QSharedPointer>
+
+class MemoryDbLink : public FileDbLink {
+public:
+  virtual void addFile(const QString& path, qint64 size, const QDateTime& dtime, const QCryptographicHash& hash);
+  bool exists(const QString& path)
+  {
+    return (entries.contains(path));
+  }
+
+  QStringList toStringList();
+  const QList<QSharedPointer<DBInfo> > values() const;
+
+private:
+  void addFile(const DBInfo& info);
+  bool tryAddFile(const DBInfo& info);
+  QMap<QString, QSharedPointer<DBInfo> > entries;
+};
+
+#endif //MEMORYDBLINK_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrderedPair.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,15 @@
+#ifndef ORDEREDPAIR_HPP
+#define ORDEREDPAIR_HPP
+
+#include <QtCore/QPair>
+
+template <typename S>
+class OrderedPair : public QPair<S, S>
+{
+public:
+  OrderedPair(const S& a, const S&b) : QPair<S, S>(a<b?a:b, a<b?b:a)
+  {
+  }
+};
+
+#endif //ORDEREDPAIR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PermissionException.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,12 @@
+#ifndef PERMISSIONEXCEPTION_HPP
+#define PERMISSIONEXCEPTION_HPP
+
+#include "IOException.hpp"
+
+class PermissionException : public IOException {
+public:
+  PermissionException(const QString& errorMsg) : IOException(errorMsg) {}
+
+};
+
+#endif //PERMISSIONEXCEPTION_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PrecompiledHeader.cmake	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,76 @@
+# Macro for setting up precompiled headers. Usage:
+#
+# add_precompiled_header(target header.h [FORCEINCLUDE])
+#
+# MSVC: A source file with the same name as the header must exist and
+# be included in the target (E.g. header.cpp).
+#
+# MSVC: Add FORCEINCLUDE to automatically include the precompiled
+# header file from every source file.
+#
+# GCC: The precompiled header is always automatically included from
+# every header file.
+MACRO(ADD_PRECOMPILED_HEADER _targetName _input)
+  GET_FILENAME_COMPONENT(_inputWe ${_input} NAME_WE)
+  SET(pch_source ${_inputWe}.cpp)
+  FOREACH(arg ${ARGN})
+    IF(arg STREQUAL FORCEINCLUDE)
+      SET(FORCEINCLUDE ON)
+    ELSE(arg STREQUAL FORCEINCLUDE)
+      SET(FORCEINCLUDE OFF)
+    ENDIF(arg STREQUAL FORCEINCLUDE)
+  ENDFOREACH(arg)
+
+  IF(MSVC)
+    GET_TARGET_PROPERTY(sources ${_targetName} SOURCES)
+    SET(_sourceFound FALSE)
+    FOREACH(_source ${sources})
+      SET(PCH_COMPILE_FLAGS "")
+      IF(_source MATCHES \\.\(cc|cxx|cpp\)$)
+GET_FILENAME_COMPONENT(_sourceWe ${_source} NAME_WE)
+IF(_sourceWe STREQUAL ${_inputWe})
+SET(PCH_COMPILE_FLAGS "${PCH_COMPILE_FLAGS} /Yc${_input}")
+SET(_sourceFound TRUE)
+ELSE(_sourceWe STREQUAL ${_inputWe})
+SET(PCH_COMPILE_FLAGS "${PCH_COMPILE_FLAGS} /Yu${_input}")
+IF(FORCEINCLUDE)
+SET(PCH_COMPILE_FLAGS "${PCH_COMPILE_FLAGS} /FI${_input}")
+ENDIF(FORCEINCLUDE)
+ENDIF(_sourceWe STREQUAL ${_inputWe})
+SET_SOURCE_FILES_PROPERTIES(${_source} PROPERTIES COMPILE_FLAGS "${PCH_COMPILE_FLAGS}")
+      ENDIF(_source MATCHES \\.\(cc|cxx|cpp\)$)
+    ENDFOREACH()
+    IF(NOT _sourceFound)
+      MESSAGE(FATAL_ERROR "A source file for ${_input} was not found. Required for MSVC builds.")
+    ENDIF(NOT _sourceFound)
+  ENDIF(MSVC)
+
+  IF(CMAKE_COMPILER_IS_GNUCXX)
+    GET_FILENAME_COMPONENT(_name ${_input} NAME)
+    SET(_source "${CMAKE_CURRENT_SOURCE_DIR}/${_input}")
+    SET(_outdir "${CMAKE_CURRENT_BINARY_DIR}/${_name}.gch")
+    MAKE_DIRECTORY(${_outdir})
+    SET(_output "${_outdir}/.c++")
+    
+    STRING(TOUPPER "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}" _flags_var_name)
+    SET(_compiler_FLAGS ${${_flags_var_name}})
+    
+    GET_DIRECTORY_PROPERTY(_directory_flags INCLUDE_DIRECTORIES)
+    FOREACH(item ${_directory_flags})
+      LIST(APPEND _compiler_FLAGS "-I${item}")
+    ENDFOREACH(item)
+
+    GET_DIRECTORY_PROPERTY(_directory_flags DEFINITIONS)
+    LIST(APPEND _compiler_FLAGS ${_directory_flags})
+
+    SEPARATE_ARGUMENTS(_compiler_FLAGS)
+    MESSAGE("${CMAKE_CXX_COMPILER} -DPCHCOMPILE ${_compiler_FLAGS} -x c++-header -o {_output} ${_source}")
+    ADD_CUSTOM_COMMAND(
+      OUTPUT ${_output}
+      COMMAND ${CMAKE_CXX_COMPILER} ${_compiler_FLAGS} -x c++-header -o ${_output} ${_source}
+      DEPENDS ${_source} )
+    ADD_CUSTOM_TARGET(${_targetName}_gch DEPENDS ${_output})
+    ADD_DEPENDENCIES(${_targetName} ${_targetName}_gch)
+    SET_TARGET_PROPERTIES(${_targetName} PROPERTIES COMPILE_FLAGS "-include ${_name} -Winvalid-pch")
+  ENDIF(CMAKE_COMPILER_IS_GNUCXX)
+ENDMACRO()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProtectedContainer.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,12 @@
+#ifndef PROTECTEDCONTAINER_HPP
+#define PROTECTEDCONTAINER_HPP
+
+#include <QtCore/QMap>
+
+template <typename Key_t, typename Value_t>
+class ProtectedDictionary {
+
+  QMap<Key_t, Value_t> map;
+};
+
+#endif //PROTECTEDCONTAINER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProtectedDictionary.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,12 @@
+#ifndef PROTECTEDCONTAINER_HPP
+#define PROTECTEDCONTAINER_HPP
+
+#include <QtCore/QMap>
+
+template <typename Key_t, typename Value_t>
+class ProtectedDictionary {
+
+  QMap<Key_t, Value_t> map;
+};
+
+#endif //PROTECTEDCONTAINER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TestEditDistance.cpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,23 @@
+#include "EditDistance.hpp"
+#include "TestFramework.hpp"
+
+BOOST_AUTO_TEST_CASE( TestEditDistance )
+{
+  BOOST_REQUIRE_EQUAL(EditDistance::Compute("kitten", "sitting", false), 3);
+  BOOST_REQUIRE_EQUAL(EditDistance::Compute("Saturday", "Sunday", false), 3);
+  BOOST_REQUIRE_EQUAL(EditDistance::Compute("kitten", "kitten.cpp.o", false), 6);
+  BOOST_REQUIRE_EQUAL(EditDistance::Compute(QString::fromUtf8("kítten"), "sitting", false), 4);
+}
+
+BOOST_AUTO_TEST_CASE( TestEditDistanceRemoveDiacritics )
+{
+  BOOST_REQUIRE_EQUAL(EditDistance::Compute(QString::fromUtf8("kítten"), "sitting", true), 3);
+}
+
+BOOST_AUTO_TEST_CASE( TestNormalization )
+{
+  BOOST_REQUIRE_EQUAL(EditDistance::removeDiacritics("kitten"), "kitten");
+  BOOST_REQUIRE_EQUAL(EditDistance::removeDiacritics(QString::fromUtf8("Händel")), "Handel");
+  BOOST_REQUIRE_EQUAL(EditDistance::removeDiacritics(QString::fromUtf8("Hånda")), "Handa");
+  BOOST_REQUIRE_EQUAL(EditDistance::removeDiacritics(QString::fromUtf8("Líll")), "Lill");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TestFramework.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,27 @@
+#ifndef TESTFRAMEWORK_HPP
+#define TESTFRAMEWORK_HPP
+
+/*
+  This header sets up everything for the testframework e.g. switches
+  between headerincludes and dynamic linking
+ */
+
+//This should really be defined from the build framework
+#define BOOSTTEST_HEADER_INCLUDE
+
+#define BOOST_TEST_MAIN
+
+#ifdef BOOSTTEST_HEADER_INCLUDE
+#include <boost/test/included/unit_test.hpp>
+#else
+#include <boost/test/unit_test.hpp>
+#endif
+
+//Here comes our helperfunctions
+#include <QtCore/QString>
+inline std::ostream& operator<<(std::ostream& out, const QString& rhs)
+{
+  return out << rhs.toStdString();
+}
+
+#endif //TESTFRAMEWORK_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ThreadSafeLookup.hpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,65 @@
+#ifndef THREADSAFELOOKUP_HPP
+#define THREADSAFELOOKUP_HPP
+
+#include <QtCore/QMap>
+
+#include <QtCore/QMutex>
+
+#include <boost/optional.hpp>
+
+struct Nothing {
+  Nothing() {}
+  Nothing(const Nothing&) {}
+  Nothing(const Nothing*) {}
+};
+
+template <bool Locks>
+struct Locking {
+  typedef QMutex Lock_t;
+  typedef QMutexLocker Locker_t;
+};
+
+template <>
+struct Locking<false> {
+  typedef Nothing Lock_t;
+  typedef Nothing Locker_t;
+};
+
+template <typename Key_t, typename Value_t, bool isLocking = true>
+class ThreadSafeLookup {
+
+private:
+  typedef QHash<Key_t, Value_t> map_t;
+  map_t map;
+
+  typename Locking<isLocking>::Lock_t masterLock;
+public:
+
+  boost::optional<Value_t> value(const Key_t& key)
+  {
+    boost::optional<Value_t> retVal;
+    typename Locking<isLocking>::Locker_t lock(&masterLock);
+    typename map_t::const_iterator c = map.find(key);
+    if (c != map.end()) {
+      retVal = c.value();
+    }
+    return retVal;
+  }
+
+  bool insert(const Key_t& key, const Value_t& value, bool forceInsert = false)
+  {
+    typename Locking<isLocking>::Locker_t lock(&masterLock);
+    typename map_t::iterator c = map.find(key);
+    bool exists = (c != map.end());
+    
+    if (exists) {
+      c.value() = value;
+    }
+    else {
+      map.insert(key, value);
+    }
+    return exists;
+  }
+};
+
+#endif //THREADSAFELOOKUP_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Aug 20 15:49:48 2012 +0200
@@ -0,0 +1,32 @@
+#include "MemoryDbLink.hpp"
+
+#include "PermissionException.hpp"
+#include "DataController.hpp"
+
+#include <QtGui/QApplication>
+#include <QtCore/QDir>
+
+#include <QtCore/QDebug>
+#include <QtCore/QCryptographicHash>
+#include <QtCore/QDateTime>
+
+#include <QtGui/QMainWindow>
+#include <QtGui/QTableWidget>
+#include <QtGui/QHeaderView>
+
+#include <cassert>
+
+
+
+int main(int argc, char *argv[]) {
+
+  QApplication app(argc, argv);
+
+
+  DataController dc;
+
+
+  return app.exec();
+}
+
+