changeset 16:06166d6c083b

Add configuration processing. Cache DB values Add a custom RBTree to save space. Track multiple DB connections properly. More testing. Add ValueExistsException.
author Tom Fredrik Blenning Klaussen <bfg@blenning.no>
date Tue, 28 Aug 2012 18:58:02 +0200
parents 199fc63c60c1
children 9a1825df8418
files CMakeLists.txt CompileTimeConstants.h ConfigurationProcessing.cpp ConfigurationProcessing.hpp DBCache.hpp DataController.cpp EditDistance.cpp EditDistance.hpp Exception.hpp RBTree.hpp SqliteDBLink.cpp TestDBCache.cpp TestEditDistance.cpp TestRBTree.cpp ThreadSafeLookup.hpp UniqueString.cpp UniqueString.hpp ValueExistsException.hpp
diffstat 18 files changed, 1081 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Sat Aug 25 01:42:13 2012 +0200
+++ b/CMakeLists.txt	Tue Aug 28 18:58:02 2012 +0200
@@ -30,6 +30,20 @@
 	FileDBLink.cpp
 	SqliteDBLink.cpp
 	MemoryDBLink.cpp
+	ConfigurationProcessing.cpp
+	UniqueString.cpp
+)
+
+SET(CLASS_HEADERS
+	DataController.hpp
+	EditDistance.hpp
+	IOException.hpp
+	FileDBLink.hpp
+	SqliteDBLink.hpp
+	MemoryDBLink.hpp
+	ConfigurationProcessing.hpp
+	UniqueString.hpp
+	RBTree.hpp
 )
 
 SET(MOC_HEADERS
@@ -41,6 +55,7 @@
 
 SET(SOURCES
 	${CLASS_SOURCES}
+	${CLASS_HEADERS}
 	${MOC_SOURCES}
 )
 
@@ -52,7 +67,7 @@
 
 
 
-SET(CMAKE_CXX_FLAGS "-g2 -Wall -Werror -fno-inline")
+SET(CMAKE_CXX_FLAGS "-g2 -Wall -fno-inline")
 ADD_EXECUTABLE(DeDupe DeDupe.cpp ${SOURCES} ${MOC_SOURCES})
 TARGET_LINK_LIBRARIES(DeDupe ${QT_LIBRARIES} ${SQLITE3_LIBRARIES} ${Boost_LIBRARIES})
 
@@ -65,8 +80,17 @@
 ADD_TEST(TestEditDistance TestEditDistance)
 TARGET_LINK_LIBRARIES(TestEditDistance ${QT_LIBRARIES} ${Boost_LIBRARIES})
 
+#ADD_EXECUTABLE(TestDBCache TestDBCache.cpp ${TEST_SOURCES})
+ADD_EXECUTABLE(TestDBCache TestDBCache.cpp)
+ADD_TEST(TestDBCache TestDBCache)
+TARGET_LINK_LIBRARIES(TestDBCache ${QT_LIBRARIES} ${Boost_LIBRARIES})
+
 ADD_EXECUTABLE(TestSqliteDBLink TestSqliteDBLink.cpp ${TEST_SOURCES})
 ADD_TEST(TestSqliteDBLink TestSqliteDBLink)
 TARGET_LINK_LIBRARIES(TestSqliteDBLink ${QT_LIBRARIES} ${Boost_LIBRARIES} )
 
+ADD_EXECUTABLE(TestRBTree TestRBTree.cpp ${TEST_SOURCES})
+ADD_TEST(TestRBTree TestRBTree)
+TARGET_LINK_LIBRARIES(TestRBTree ${QT_LIBRARIES} ${Boost_LIBRARIES} )
+
 #ADD_PRECOMPILED_HEADER(TestEditDistance TestFramework.hpp)
--- a/CompileTimeConstants.h	Sat Aug 25 01:42:13 2012 +0200
+++ b/CompileTimeConstants.h	Tue Aug 28 18:58:02 2012 +0200
@@ -5,6 +5,10 @@
 #define DB_DEFAULT_LOCATION "~/.DeDupe.sqlite"
 #endif
 
+#ifndef EDITDISTANCE_CACHE_LOCATION
+#define EDITDISTANCE_CACHE_LOCATION "~/.EditDistance.sqlite"
+#endif
+
 #ifndef USE_BOOST_FIND
 #define USE_BOOST_FIND 1
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ConfigurationProcessing.cpp	Tue Aug 28 18:58:02 2012 +0200
@@ -0,0 +1,13 @@
+#include "ConfigurationProcessing.hpp"
+
+#include <QtGui/QDesktopServices>
+#include <QtCore/QRegExp>
+#include <QtCore/QDir>
+
+QString processSetupVariables(const QString& inString)
+{
+  QString outString = inString;
+  outString.replace(QRegExp("^~/"),
+		    QString("%1%2").arg(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)).arg(QDir::separator()));
+  return outString;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ConfigurationProcessing.hpp	Tue Aug 28 18:58:02 2012 +0200
@@ -0,0 +1,8 @@
+#ifndef CONFIGURATIONPROCESSING_HPP
+#define CONFIGURATIONPROCESSING_HPP
+
+#include <QtCore/QString>
+
+QString processSetupVariables(const QString& inString);
+
+#endif //CONFIGURATIONPROCESSING_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DBCache.hpp	Tue Aug 28 18:58:02 2012 +0200
@@ -0,0 +1,360 @@
+#ifndef DBCACHE_HPP
+#define DBCACHE_HPP
+
+#include <QtSql/QSqlDatabase>
+#include <QtSql/QSqlQuery>
+#include <QtSql/QSqlError>
+#include <QtSql/QSqlRecord>
+
+#include <QtCore/QDebug>
+#include <QtCore/QStringList>
+#include <cassert>
+#include "OrderedPair.hpp"
+
+#include <boost/optional.hpp>
+
+#include "ThreadSafeLookup.hpp"
+#include "UniqueString.hpp"
+
+
+template<typename T>
+struct SQLGenerator
+{
+};
+
+template<>
+struct SQLGenerator<int>
+{
+  static QString fieldName(const QString &prefix = QString())
+  {
+    return QString("%1_Int").arg(prefix);
+  }
+
+  static QString createFields(const QString &prefix = QString())
+  {
+    return QString("%1 INTEGER").arg(fieldName(prefix));
+  }
+
+  static QString valueString(const QString &prefix = QString())
+  {
+    return QString(":") + fieldName(prefix);
+  }
+
+  static void bindValue(QSqlQuery& query, int value, const QString& prefix = QString())
+  {
+    query.bindValue(valueString(prefix), value);
+  }
+
+  static void bindValues(QSqlQuery& query, const QVariantList& values, const QString& prefix = QString())
+  {
+    query.bindValue(valueString(prefix), values);
+  }
+
+  static void bindValues(QSqlQuery& query, const QList<int>& values, const QString& prefix = QString())
+  {
+    QVariantList list;
+    foreach(int value, values) {
+      list << value;
+    }
+    bindValues(query, list, prefix);
+  }
+
+  static boost::optional<int> extract(QSqlQuery& query, const QString& prefix = QString())
+  {
+    int fieldNo = query.record().indexOf(fieldName(prefix));
+    if (query.at() >= 0)
+      return query.value(fieldNo).toInt();
+    else
+      return boost::optional<int>();
+  }
+
+};
+
+template<>
+struct SQLGenerator<QString>
+{
+  static QString fieldName(const QString &prefix = QString())
+  {
+    return QString("%1_QString").arg(prefix);
+  }
+
+  static QString createFields(const QString &prefix = QString())
+  {
+    return QString("%1 TEXT").arg(fieldName(prefix));
+  }
+
+  static QString valueString(const QString &prefix = QString())
+  {
+    return QString(":") + fieldName(prefix);
+  }
+
+  static QString restrict(const QString &prefix = QString())
+  {
+    return QString("%1 = %2").arg(fieldName(prefix)).arg(valueString(prefix));
+  }
+
+  static void bindValue(QSqlQuery& query, const QString& value, const QString& prefix = QString())
+  {
+    query.bindValue(valueString(prefix), value);
+  }
+
+  static void bindValues(QSqlQuery& query, const QVariantList& value, const QString& prefix = QString())
+  {
+    query.bindValue(valueString(prefix), value);
+  }
+
+  static void bindValues(QSqlQuery& query, const QList<QString>& values, const QString& prefix = QString())
+  {
+    QVariantList list;
+    foreach(const QString& value, values) {
+      list << value;
+    }
+    bindValues(query, list, prefix);
+  }
+
+  static boost::optional<QString> extract(QSqlQuery& query, const QString& prefix = QString())
+  {
+    int fieldNo = query.record().indexOf(fieldName(prefix));
+    if (query.at() >= 0)
+      return query.value(fieldNo).toString();
+    else
+      return boost::optional<QString>();
+  }
+
+
+};
+
+template<>
+struct SQLGenerator<UniqueString>
+{
+  static QString fieldName(const QString &prefix = QString())
+  {
+    return QString("%1_QString").arg(prefix);
+  }
+
+  static QString createFields(const QString &prefix = QString())
+  {
+    return QString("%1 TEXT").arg(fieldName(prefix));
+  }
+
+  static QString valueString(const QString &prefix = QString())
+  {
+    return QString(":") + fieldName(prefix);
+  }
+
+  static QString restrict(const QString &prefix = QString())
+  {
+    return QString("%1 = %2").arg(fieldName(prefix)).arg(valueString(prefix));
+  }
+
+  static void bindValue(QSqlQuery& query, const QString& value, const QString& prefix = QString())
+  {
+    query.bindValue(valueString(prefix), value);
+  }
+
+  static void bindValues(QSqlQuery& query, const QVariantList& value, const QString& prefix = QString())
+  {
+    query.bindValue(valueString(prefix), value);
+  }
+
+  static void bindValues(QSqlQuery& query, const QList<UniqueString>& values, const QString& prefix = QString())
+  {
+    QVariantList list;
+    foreach(const QString& value, values) {
+      list << value;
+    }
+    bindValues(query, list, prefix);
+  }
+
+  static boost::optional<QString> extract(QSqlQuery& query, const QString& prefix = QString())
+  {
+    int fieldNo = query.record().indexOf(fieldName(prefix));
+    if (query.at() >= 0)
+      return query.value(fieldNo).toString();
+    else
+      return boost::optional<QString>();
+  }
+
+
+};
+
+template<typename T>
+struct SQLGenerator<OrderedPair<T> >
+{
+  static QString fieldName(const QString &prefix = QString())
+  {
+    return SQLGenerator<T>::fieldName(prefix + "_1") + ", " +
+      SQLGenerator<T>::fieldName(prefix + "_2");
+  }
+
+  static QString createFields(const QString &prefix = QString())
+  {
+    return SQLGenerator<T>::createFields(prefix + "_1") + ", " +
+      SQLGenerator<T>::createFields(prefix + "_2");
+  }
+
+  static QString restriction(const QString& prefix = QString())
+  {
+    return SQLGenerator<T>::restrict(prefix + "_1") + " AND " +
+      SQLGenerator<T>::restrict(prefix + "_2");    
+  }
+
+  static QString valueString(const QString &prefix = QString())
+  {
+    return SQLGenerator<T>::valueString(prefix + "_1") + ", " +
+      SQLGenerator<T>::valueString(prefix + "_2");
+  }
+
+  static void bindValue(QSqlQuery& query, const OrderedPair<T>& value, const QString& prefix = QString())
+  {
+    SQLGenerator<T>::bindValue(query, value.first, prefix + "_1");
+    SQLGenerator<T>::bindValue(query, value.second, prefix + "_2");
+  }
+
+  static void bindValues(QSqlQuery& query, const QList<OrderedPair<T> >& values, const QString& prefix = QString())
+  {
+    QList<T> first;
+    QList<T> second;
+    foreach(OrderedPair<T> value, values) {
+      first << value.first;
+      second << value.second;
+    }
+    SQLGenerator<T>::bindValues(query, first, prefix + "_1");
+    SQLGenerator<T>::bindValues(query, second, prefix + "_2");
+  }
+
+  static boost::optional<OrderedPair<T> > extract(QSqlQuery& query, const QString& prefix = QString())
+  {
+    if (query.at() >= 0)
+      return OrderedPair<T> (*SQLGenerator<T>::extract(query, prefix + "_1"),
+			     *SQLGenerator<T>::extract(query, prefix + "_2"));
+    else
+      return boost::optional<OrderedPair<T> >();
+  }
+
+
+};
+
+template<typename Key, typename Value, bool memoryMapped = false>
+class DBCache
+{
+private:
+  ThreadSafeLookup<Key, Value> memoryMap;
+  QList<Key> unsyncedKeys;
+
+  void setup(const QSqlDatabase& db, const QString& dictName)
+  {
+    this->dictName = dictName;
+    if (!db.tables().contains(dictName)) {
+      QString keyFields = SQLGenerator<Key>::createFields("key");
+      QString valueFields = SQLGenerator<Value>::createFields("value");
+      QString createQuery = QString("CREATE TABLE %1(%2, %3);").arg(dictName).arg(keyFields).arg(valueFields);
+      QSqlQuery query(db);
+      query.exec(createQuery);
+    }
+    if (!db.tables().contains(dictName)) {
+      qDebug()<<"No database";
+      exit(1);
+    }
+    if (memoryMapped) {
+      QString keyFields = SQLGenerator<Key>::fieldName("key");
+      QString valueFields = SQLGenerator<Value>::fieldName("value");
+      QString repopulateQuery = QString("SELECT %1, %2 FROM %3;").arg(keyFields).arg(valueFields).arg(dictName);
+      QSqlQuery query(db);
+      if (!query.exec(repopulateQuery)) {
+	qDebug() << query.lastError() << repopulateQuery;
+      }
+      while (query.next()) {
+	Key key = *SQLGenerator<Key>::extract(query, "key");
+	Value value = *SQLGenerator<Value>::extract(query, "value");
+	memoryMap.insert(key, value);
+      }
+    }
+    QString queryString = QString("INSERT into %1 (%2, %3) VALUES(%4, %5);")
+      .arg(dictName)
+      .arg(SQLGenerator<Key>::fieldName("key"))
+      .arg(SQLGenerator<Value>::fieldName("value"))
+      .arg(SQLGenerator<Key>::valueString("key"))
+      .arg(SQLGenerator<Value>::valueString("value"));
+    insertQuery = QSqlQuery(db);
+    insertQuery.prepare(queryString);
+  }
+
+  void syncInsert()
+  {
+    SQLGenerator<Key>::bindValues(insertQuery, unsyncedKeys, "key");
+    QList<Value> values;
+    foreach(Key key, unsyncedKeys) {
+      values << *memoryMap.value(key);
+    }
+    SQLGenerator<Value>::bindValues(insertQuery, values, "value");
+    if (!insertQuery.exec()) {
+      qDebug() << insertQuery.lastError() << insertQuery.lastQuery();
+    }
+    insertQuery.finish();
+    unsyncedKeys.clear();
+  }
+
+public:
+  DBCache(const QString& dbName, const QString& dictName) 
+  {
+    db = QSqlDatabase::addDatabase("QSQLITE", "dictName");
+    db.setDatabaseName(dbName);
+    bool ok = db.open();
+    assert(ok);
+    setup(db, dictName);
+  }
+
+  DBCache(const QSqlDatabase& db, const QString& dictName) 
+  {
+    setup(db, dictName);
+  }
+
+  boost::optional<Value> value(const Key& key) const
+  {
+    if (memoryMapped) {
+      return memoryMap.value(key);
+    }
+    else {
+      QString queryString = QString("SELECT %1 FROM %2 WHERE %3")
+	.arg(SQLGenerator<Value>::fieldName("value"))
+	.arg(dictName)
+	.arg(SQLGenerator<Key>::restriction("key"));
+      QSqlQuery query(db);
+      query.prepare(queryString);
+      SQLGenerator<Key>::bindValue(query, key, "key");
+      if (!query.exec()) {
+	qDebug() << query.lastError() << queryString;
+      }
+      query.next();
+      return SQLGenerator<Value>::extract(query, "value");
+    }
+  }
+
+  void insert(const Key& key, const Value& value)
+  {
+    if (memoryMapped) {
+      memoryMap.insert(key, value);
+      unsyncedKeys.append(key);
+      if (unsyncedKeys.size() > 1024) {
+	syncInsert();
+      }
+    }
+    else {
+      SQLGenerator<Key>::bindValue(insertQuery, key, "key");
+      SQLGenerator<Value>::bindValue(insertQuery, value, "value");
+      if (!insertQuery.exec()) {
+	qDebug() << insertQuery.lastError() << insertQuery.lastQuery();
+      }
+      insertQuery.finish();
+    }
+      
+  }
+
+private:
+  QSqlDatabase db;
+  QString dictName;
+  QSqlQuery insertQuery;
+};
+
+#endif //DBCACHE_HPP
--- a/DataController.cpp	Sat Aug 25 01:42:13 2012 +0200
+++ b/DataController.cpp	Tue Aug 28 18:58:02 2012 +0200
@@ -4,6 +4,7 @@
 #include "MemoryDBLink.hpp"
 #include "PermissionException.hpp"
 #include "SqliteDBLink.hpp"
+#include "ConfigurationProcessing.hpp"
 
 #include <QtCore/QCryptographicHash>
 #include <QtCore/QDateTime>
@@ -446,7 +447,6 @@
   QDesktopServices::openUrl(url);  
 }
 
-
 void DataController::setup(const QString& dbpath_in, const QString& searchPath_in, bool showGUI)
 {
   this->showGUI = showGUI;
@@ -460,10 +460,7 @@
     dbpath = dbpath_in;
   }
   else {
-    dbpath = DB_DEFAULT_LOCATION;
-    
-    dbpath.replace(QRegExp("^~/"),
-		   QString("%1%2").arg(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)).arg(QDir::separator()));
+    dbpath = processSetupVariables(DB_DEFAULT_LOCATION);
   }
 
 #if 1
--- a/EditDistance.cpp	Sat Aug 25 01:42:13 2012 +0200
+++ b/EditDistance.cpp	Tue Aug 28 18:58:02 2012 +0200
@@ -1,10 +1,13 @@
 #include "EditDistance.hpp"
+#include "CompileTimeConstants.h"
+#include "ConfigurationProcessing.hpp"
 
 #include <QtCore/QList>
 
 #define CharComparer(A, B) (QChar(A) == QChar(B))
 
-EditDistance::cacheType EditDistance::cache;
+EditDistance::cacheType* EditDistance::cache = 0;
+//EditDistance::cacheType EditDistance::cache;
 
 QString EditDistance::removeDiacritics(QString in)
 {
@@ -27,9 +30,13 @@
     b = removeDiacritics(b);
   }
 
-  OrderedPair<QString> lup(a, b);
+  OrderedPair<UniqueString> lup(a, b);
 
-  boost::optional<int> res = cache.value(lup);
+  if (cache == 0) {
+    QString cacheLocation = processSetupVariables(EDITDISTANCE_CACHE_LOCATION);
+    EditDistance::cache = new cacheType(cacheLocation, "EditLUT");
+  }
+  boost::optional<int> res = cache->value(lup);
   if (res)
     return *res;
 
@@ -74,7 +81,7 @@
   
   // Return final value
   int retVal = d[a.size()][ b.size()];
-  cache.insert(lup, retVal);
+  cache->insert(lup, retVal);
 
   return retVal;
 }
--- a/EditDistance.hpp	Sat Aug 25 01:42:13 2012 +0200
+++ b/EditDistance.hpp	Tue Aug 28 18:58:02 2012 +0200
@@ -2,6 +2,8 @@
 #define EDITDISTANCE_HPP
 
 #include "OrderedPair.hpp"
+#include "DBCache.hpp"
+#include "UniqueString.hpp"
 
 #include <QtCore/QString>
 #include <QtCore/QMap>
@@ -11,14 +13,16 @@
 
 class EditDistance {
 protected:
-  typedef ThreadSafeLookup<OrderedPair<QString>, int> cacheType;
+  //typedef ThreadSafeLookup<OrderedPair<QString>, int> cacheType;
+  typedef DBCache<OrderedPair<UniqueString>, int, true> 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; 
+  //static cacheType cache; 
+  static cacheType* cache;
 };
 
 #endif //EDITDISTANCE_HPP
--- a/Exception.hpp	Sat Aug 25 01:42:13 2012 +0200
+++ b/Exception.hpp	Tue Aug 28 18:58:02 2012 +0200
@@ -19,7 +19,7 @@
 
 
 protected:
-  Exception(const string_t& errorMsg) : errorMsg_(errorMsg) {}
+  Exception(const string_t& errorMsg = string_t()) : errorMsg_(errorMsg) {}
   virtual ~Exception() {}
 
   void setErrorMsg(string_t& errorMsg);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RBTree.hpp	Tue Aug 28 18:58:02 2012 +0200
@@ -0,0 +1,344 @@
+#ifndef RBTREE_HPP
+#define RBTREE_HPP
+
+#include <boost/optional.hpp>
+#include <cmath>
+#include "ValueExistsException.hpp"
+
+#include <QtCore/QDebug>
+
+#define CONSISTENCY_CHECKING 0
+
+#if CONSISTENCY_CHECKING
+#define CONSISTENCY_CHECK(n) assert(!n || (n)->consistent())
+#else
+#define CONSISTENCY_CHECK(n)
+#endif
+
+template<typename Data>
+class RBTree {
+private:
+  class RBTreeNode {
+    Data data;
+    RBTreeNode* left;
+    RBTreeNode *right;
+    RBTreeNode *parent;
+    bool red;
+
+    enum Color { RED, BLACK };
+
+  public:
+    RBTreeNode(const Data& data) : left(0), right(0), parent(0), red(true)
+    {
+      this->data = data;
+    }
+
+    void setColor(Color color)
+    {
+      red = (color == RED);
+    }
+
+    Color color() const
+    {
+      return red ? RED : BLACK;
+    }
+
+    RBTreeNode* grandparent()
+    {
+      if ((this != NULL) && (this->parent != NULL))
+	return this->parent->parent;
+      else
+	return NULL;
+    }
+ 
+    RBTreeNode* uncle()
+    {
+      RBTreeNode* g = grandparent();
+      if (g == NULL)
+	return NULL; // No grandparent means no uncle
+      if (this->parent == g->left)
+	return g->right;
+      else
+	return g->left;
+    }
+
+    RBTreeNode* getRootNode()
+    {
+      RBTreeNode* p = this;
+      while(p->parent)
+	p = p->parent;
+      return p;
+    }
+
+    void rotate_left()
+    {
+      RBTreeNode* Q = this->right;
+      assert(Q);
+      //      Set Q to be the new root.
+      if (this->parent) {
+	if (this->parent->left == this) {
+	  this->parent->left = Q;
+	}
+	else {
+	  assert(this->parent->right == this);
+	  this->parent->right = Q;
+	}
+      }
+      this->right = Q->left;
+      if (this->right)
+	this->right->parent = this;
+      Q->left = this;
+      Q->parent = this->parent;
+      this->parent = Q;
+    }
+
+    void rotate_right()
+    {
+      RBTreeNode* P = this->left;
+      assert(P);
+      //Set P to be the new root.
+      if (this->parent) {
+	if (this->parent->left == this) {
+	  this->parent->left = P;
+	}
+	else {
+	  assert(this->parent->right == this);
+	  this->parent->right = P;
+	}
+      }
+      this->left = P->right;
+      if (this->left)
+	this->left->parent = this;
+      P->right = this;
+      P->parent = this->parent;
+      this->parent = P;
+    }
+
+    void insert_case5()
+    {
+      RBTreeNode* g = grandparent();
+      CONSISTENCY_CHECK(this);
+      CONSISTENCY_CHECK(g);
+      
+      this->parent->setColor(BLACK);
+      g->setColor(RED);
+      if (this == this->parent->left)
+	g->rotate_right();
+      else {
+	assert (this == this->parent->right);
+	g->rotate_left();
+      }
+    }
+    
+    void insert_case4()
+    {
+      RBTreeNode* g = grandparent();
+
+      CONSISTENCY_CHECK(this);
+
+      if (!g)
+	return;
+      CONSISTENCY_CHECK(g);
+
+      RBTreeNode* n = this;
+
+      if ((this == n->parent->right) && (n->parent == g->left)) {
+	n->parent->rotate_left();
+	n = n->left;
+      } else if ((n == n->parent->left) && (n->parent == g->right)) {
+	n->parent->rotate_right();
+	n = n->right;
+      }
+      n->insert_case5();
+    }
+
+
+    void insert_case3()
+    {
+      RBTreeNode *u = uncle();
+      CONSISTENCY_CHECK(this);
+      CONSISTENCY_CHECK(u);
+      
+      if ((u != NULL) && (u->color() == RED)) {
+	this->parent->setColor(BLACK);
+	u->setColor(BLACK);
+	RBTreeNode* g;
+	g = grandparent();
+	g->setColor(RED);
+	g->insert_case1();
+      } else {
+	insert_case4();
+      }
+    }
+
+    void insert_case2()
+    {
+      CONSISTENCY_CHECK(this);
+      if (this->parent->color() == BLACK)
+	return; /* Tree is still valid */
+      else
+	insert_case3();
+    }
+
+    void insert_case1()
+    {
+      CONSISTENCY_CHECK(this);
+
+      if (this->parent == NULL)
+	this->setColor(BLACK);
+      else
+	insert_case2();
+    }
+
+    void insert(const Data& data)
+    {
+      if (data < this->data) {
+	if (!left) {
+	  left = new RBTreeNode(data);
+	  left->parent = this;
+	  left->insert_case1();
+	}
+	else
+	  left->insert(data);
+      }
+      else if (data > this->data) {
+	if (!right) {
+	  right = new RBTreeNode(data);
+	  right->parent = this;
+	  right->insert_case1();
+	}
+	else
+	  right->insert(data);	
+      }
+      else {
+	throw ValueExistsException();
+      }
+      CONSISTENCY_CHECK(this);
+    }
+
+    boost::optional<Data> find(const Data& data)
+    {
+      if (this->data == data) {
+	return this->data;
+      }
+      else if (data < this->data) {
+	if (left)
+	  return left->find(data);
+	else
+	  return boost::optional<Data>();
+      }
+      else {
+	if (right)
+	  return right->find(data);
+	else
+	  return boost::optional<Data>();
+      }
+      return boost::optional<Data>();
+    }
+
+    uint size() const
+    {
+      return 1 + (left ? left->size() : 0) + (right ? right->size() : 0);
+    }
+
+    uint depth() const
+    {
+      return 1 + std::max((left ? left->depth() : 0), (right ? right->depth() : 0));
+    }
+
+    uint total_depth(uint n) const
+    {
+      return n + (left ? left->total_depth(n + 1) : 0) + (right ? right->total_depth(n + 1) : 0);
+    }
+
+    bool consistent() const 
+    {
+      bool retVal = true;
+      if (left)
+	retVal = retVal && (left->parent == this) && left->consistent();
+      if (right)
+	retVal = retVal && (right->parent == this) && right->consistent();
+      return retVal;
+    }
+
+    friend class RBTree;
+
+  };
+  RBTreeNode* node;
+public:
+  RBTree() : node(0)
+  {
+  }
+  
+  bool consistent() const 
+  {
+    if (node == 0)
+      return true;
+    if (node->parent == 0 && node->consistent())
+      return true;
+    return false;
+  }
+
+  void insert(const Data& data)
+  {
+    if (node == 0) {
+      node = new RBTreeNode(data);
+    }
+    else {
+      node->insert(data);
+    }
+    node = node->getRootNode();
+    CONSISTENCY_CHECK(this);
+  }
+  boost::optional<Data> find(const Data& data)
+  {
+    if (node) {
+      return node->find(data);
+    }
+    else
+      return boost::optional<Data>();
+  }
+
+  uint size() const
+  {
+    if (!node)
+      return 0;
+    else
+      return node->size();
+  }
+
+  uint depth() const
+  {
+    if (!node)
+      return 0;
+    return node->depth();
+  }
+
+  float avg_depth() const
+  {
+    if (!node)
+      return 0;
+    return double(total_depth())/size();
+  }
+
+  uint total_depth() const
+  {
+    if (!node)
+      return 0;
+    return node->total_depth(1);
+  }
+
+  uint optimal_depth() const
+  {
+    int s = size();
+    if (s == 0)
+      return 0;
+    int d = s;
+    int targetlevel = 1;
+    while (d >>= 1)
+      ++targetlevel;
+    return targetlevel;
+  }
+};
+
+#endif //RBTREE_HPP
--- a/SqliteDBLink.cpp	Sat Aug 25 01:42:13 2012 +0200
+++ b/SqliteDBLink.cpp	Tue Aug 28 18:58:02 2012 +0200
@@ -11,11 +11,11 @@
 
 SqliteDBLink::SqliteDBLink(const QString& dbPath)
 {
-  db = QSqlDatabase::addDatabase("QSQLITE");
+  db = QSqlDatabase::addDatabase("QSQLITE", "SqliteDBLink");
   db.setDatabaseName(dbPath);
   bool ok = db.open();
   assert(ok);
-  QSqlQuery query;
+  QSqlQuery query(db);
   if (!query.exec(QString("SELECT * FROM files;"))) {
     query.exec("CREATE TABLE files(path VARCHAR PRIMARY KEY ASC, size INTEGER, mtime TEXT, checksum TEXT);");
   }
@@ -33,7 +33,7 @@
 
 bool SqliteDBLink::exists(const QString& path)
 {
-  QSqlQuery query;
+  QSqlQuery query(db);
   query.prepare("SELECT path FROM files WHERE path = :path;");
   query.bindValue(":path", path);
   if (!query.exec()) {
@@ -44,7 +44,7 @@
 
 FileDBLink::DBStatus SqliteDBLink::existsWithMtime(const QString& path, const QDateTime& mtime)
 {
-  QSqlQuery query;
+  QSqlQuery query(db);
   query.prepare("SELECT mtime FROM files WHERE path = :path;");
   query.bindValue(":path", path);
   if (!query.exec()) {
@@ -69,7 +69,7 @@
 {
   if (exists(dbinfo.path()))
     return false;
-  QSqlQuery query;
+  QSqlQuery query(db);
   query.prepare("INSERT INTO files (path, size, mtime, checksum) "
 		"VALUES (:path, :size, :mtime, :checksum)");
   query.bindValue(":path", dbinfo.path());
@@ -89,7 +89,7 @@
 
 void SqliteDBLink::updateFile(const DBInfo& dbinfo)
 {
-  QSqlQuery query;
+  QSqlQuery query(db);
   query.prepare("UPDATE files SET size=:size, mtime=:mtime, checksum=:checksum WHERE path=:path");
   query.bindValue(":path", dbinfo.path());
   query.bindValue(":size", dbinfo.size());
@@ -124,7 +124,7 @@
 {
   QList<QSharedPointer<FileDBLink::DBInfo> > values;
 
-  QSqlQuery query;
+  QSqlQuery query(db);
 
   if (prefix.size() > 0) {
     query.prepare("SELECT * FROM files WHERE path LIKE :prefix");
@@ -158,12 +158,12 @@
 
 void SqliteDBLink::deleteFileFromDB(const QString& path)
 {
-  QSqlQuery query;
-    query.prepare("DELETE FROM files WHERE path = :path");
-    query.bindValue(":path", path);
-    if (!query.exec()) {
-      qDebug() << path << "::" << query.lastQuery() << "::" << query.lastError().text();
-    }
+  QSqlQuery query(db);
+  query.prepare("DELETE FROM files WHERE path = :path");
+  query.bindValue(":path", path);
+  if (!query.exec()) {
+    qDebug() << path << "::" << query.lastQuery() << "::" << query.lastError().text();
+  }
 }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TestDBCache.cpp	Tue Aug 28 18:58:02 2012 +0200
@@ -0,0 +1,29 @@
+#include "DBCache.hpp"
+#include "OrderedPair.hpp"
+#include "TestFramework.hpp"
+
+#include <QtCore/QTemporaryFile>
+
+BOOST_AUTO_TEST_CASE( TestCache )
+{
+  QTemporaryFile sqlfile("XXXXXX.sqlite");
+  sqlfile.setAutoRemove(false);
+  sqlfile.open();
+
+  typedef OrderedPair<QString> key_t;
+
+  DBCache<key_t, int> cache (sqlfile.fileName(), "SomeLUT");
+  key_t key1("hei", "hei");
+  key_t key2("S'turday", "Sunday");
+
+  bool exists = cache.value(key1);
+  BOOST_REQUIRE_EQUAL(exists, false);
+  cache.insert(key1, 321);
+  BOOST_REQUIRE_EQUAL(cache.value(key1), 321);
+
+  exists = cache.value(key2);
+  BOOST_REQUIRE_EQUAL(exists, false);
+  cache.insert(key2, 19);
+  BOOST_REQUIRE_EQUAL(cache.value(key2), 19);
+
+}
--- a/TestEditDistance.cpp	Sat Aug 25 01:42:13 2012 +0200
+++ b/TestEditDistance.cpp	Tue Aug 28 18:58:02 2012 +0200
@@ -21,3 +21,9 @@
   BOOST_REQUIRE_EQUAL(EditDistance::removeDiacritics(QString::fromUtf8("Hånda")), "Handa");
   BOOST_REQUIRE_EQUAL(EditDistance::removeDiacritics(QString::fromUtf8("Líll")), "Lill");
 }
+
+BOOST_AUTO_TEST_CASE( TestStrangeChars )
+{
+  BOOST_REQUIRE_EQUAL(EditDistance::Compute("S'turday", "Sunday", false), 3);
+  BOOST_REQUIRE_EQUAL(EditDistance::Compute("S'turday", "Sunday", false), 3);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TestRBTree.cpp	Tue Aug 28 18:58:02 2012 +0200
@@ -0,0 +1,196 @@
+#include "RBTree.hpp"
+#include "TestFramework.hpp"
+
+#include <QtCore/QString>
+
+BOOST_AUTO_TEST_CASE( TestInsertLeftHeavy )
+{
+  RBTree<QString> map;
+
+  QString k1 = "9";
+  QString k2 = "8";
+  QString k3 = "7";
+  QString k4 = "6";
+
+  BOOST_REQUIRE(!map.find(k1));
+  BOOST_REQUIRE_EQUAL(map.depth(), 0u);
+  BOOST_REQUIRE_EQUAL(map.total_depth(), 0u);
+  BOOST_REQUIRE_EQUAL(map.size(), 0u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 0u);
+  map.insert(k1);
+  BOOST_REQUIRE_EQUAL(map.depth(), 1u);
+  BOOST_REQUIRE_EQUAL(map.total_depth(), 1u);
+  BOOST_REQUIRE_EQUAL(map.size(), 1u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 1u);
+  BOOST_REQUIRE_THROW(map.insert(k1), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+
+  BOOST_REQUIRE(!map.find(k3));
+  map.insert(k2);
+  BOOST_REQUIRE_EQUAL(map.depth(), 2u);
+  BOOST_REQUIRE_EQUAL(map.total_depth(), 3u);
+  BOOST_REQUIRE_EQUAL(map.size(), 2u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 2u);
+  BOOST_REQUIRE_THROW(map.insert(k2), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+  BOOST_REQUIRE(map.find(k2));
+  BOOST_REQUIRE_EQUAL(*map.find(k2), k2);
+
+  BOOST_REQUIRE(!map.find(k3));
+  map.insert(k3);
+  BOOST_WARN_EQUAL(map.depth(), 2u);
+  BOOST_WARN_EQUAL(map.total_depth(), 5u);
+  BOOST_REQUIRE_EQUAL(map.size(), 3u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 2u);
+  BOOST_REQUIRE_THROW(map.insert(k3), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+  BOOST_REQUIRE(map.find(k2));
+  BOOST_REQUIRE_EQUAL(*map.find(k2), k2);
+  BOOST_REQUIRE(map.find(k3));
+  BOOST_REQUIRE_EQUAL(*map.find(k3), k3);
+
+  BOOST_REQUIRE(!map.find(k4));
+  map.insert(k4);
+  BOOST_WARN_EQUAL(map.depth(), 2u);
+  BOOST_WARN_EQUAL(map.total_depth(), 8u);
+  BOOST_REQUIRE_EQUAL(map.size(), 4u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 3u);
+  BOOST_REQUIRE_THROW(map.insert(k4), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+  BOOST_REQUIRE(map.find(k2));
+  BOOST_REQUIRE_EQUAL(*map.find(k2), k2);
+  BOOST_REQUIRE(map.find(k4));
+  BOOST_REQUIRE_EQUAL(*map.find(k4), k4);
+}
+
+BOOST_AUTO_TEST_CASE( TestInsertRightHeavy )
+{
+  RBTree<QString> map;
+
+  QString k1 = "5";
+  QString k2 = "6";
+  QString k3 = "7";
+  QString k4 = "8";
+
+  BOOST_REQUIRE(!map.find(k1));
+  BOOST_REQUIRE_EQUAL(map.depth(), 0u);
+  BOOST_REQUIRE_EQUAL(map.total_depth(), 0u);
+  BOOST_REQUIRE_EQUAL(map.size(), 0u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 0u);
+  map.insert(k1);
+  BOOST_REQUIRE_EQUAL(map.depth(), 1u);
+  BOOST_REQUIRE_EQUAL(map.total_depth(), 1u);
+  BOOST_REQUIRE_EQUAL(map.size(), 1u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 1u);
+  BOOST_REQUIRE_THROW(map.insert(k1), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+
+  BOOST_REQUIRE(!map.find(k3));
+  map.insert(k2);
+  BOOST_REQUIRE_EQUAL(map.depth(), 2u);
+  BOOST_REQUIRE_EQUAL(map.total_depth(), 3u);
+  BOOST_REQUIRE_EQUAL(map.size(), 2u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 2u);
+  BOOST_REQUIRE_THROW(map.insert(k2), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+  BOOST_REQUIRE(map.find(k2));
+  BOOST_REQUIRE_EQUAL(*map.find(k2), k2);
+
+  BOOST_REQUIRE(!map.find(k3));
+  map.insert(k3);
+  BOOST_WARN_EQUAL(map.depth(), 2u);
+  BOOST_WARN_EQUAL(map.total_depth(), 5u);
+  BOOST_REQUIRE_EQUAL(map.size(), 3u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 2u);
+  BOOST_REQUIRE_THROW(map.insert(k3), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+  BOOST_REQUIRE(map.find(k2));
+  BOOST_REQUIRE_EQUAL(*map.find(k2), k2);
+  BOOST_REQUIRE(map.find(k3));
+  BOOST_REQUIRE_EQUAL(*map.find(k3), k3);
+
+  BOOST_REQUIRE(!map.find(k4));
+  map.insert(k4);
+  BOOST_WARN_EQUAL(map.depth(), 2u);
+  BOOST_WARN_EQUAL(map.total_depth(), 8u);
+  BOOST_REQUIRE_EQUAL(map.size(), 4u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 3u);
+  BOOST_REQUIRE_THROW(map.insert(k4), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+  BOOST_REQUIRE(map.find(k2));
+  BOOST_REQUIRE_EQUAL(*map.find(k2), k2);
+  BOOST_REQUIRE(map.find(k4));
+  BOOST_REQUIRE_EQUAL(*map.find(k4), k4);
+}
+
+BOOST_AUTO_TEST_CASE( TestInsertBalanced )
+{
+  RBTree<QString> map;
+
+  QString k1 = "8";
+  QString k2 = "4";
+  QString k3 = "12";
+  QString k4 = "2";
+
+  BOOST_REQUIRE(!map.find(k1));
+  BOOST_REQUIRE_EQUAL(map.depth(), 0u);
+  BOOST_REQUIRE_EQUAL(map.total_depth(), 0u);
+  BOOST_REQUIRE_EQUAL(map.size(), 0u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 0u);
+  map.insert(k1);
+  BOOST_REQUIRE_EQUAL(map.depth(), 1u);
+  BOOST_REQUIRE_EQUAL(map.total_depth(), 1u);
+  BOOST_REQUIRE_EQUAL(map.size(), 1u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 1u);
+  BOOST_REQUIRE_THROW(map.insert(k1), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+
+  BOOST_REQUIRE(!map.find(k3));
+  map.insert(k2);
+  BOOST_REQUIRE_EQUAL(map.depth(), 2u);
+  BOOST_REQUIRE_EQUAL(map.total_depth(), 3u);
+  BOOST_REQUIRE_EQUAL(map.size(), 2u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 2u);
+  BOOST_REQUIRE_THROW(map.insert(k2), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+  BOOST_REQUIRE(map.find(k2));
+  BOOST_REQUIRE_EQUAL(*map.find(k2), k2);
+
+  BOOST_REQUIRE(!map.find(k3));
+  map.insert(k3);
+  BOOST_WARN_EQUAL(map.depth(), 2u);
+  BOOST_WARN_EQUAL(map.total_depth(), 5u);
+  BOOST_REQUIRE_EQUAL(map.size(), 3u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 2u);
+  BOOST_REQUIRE_THROW(map.insert(k3), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+  BOOST_REQUIRE(map.find(k2));
+  BOOST_REQUIRE_EQUAL(*map.find(k2), k2);
+  BOOST_REQUIRE(map.find(k3));
+  BOOST_REQUIRE_EQUAL(*map.find(k3), k3);
+
+  BOOST_REQUIRE(!map.find(k4));
+  map.insert(k4);
+  BOOST_WARN_EQUAL(map.depth(), 2u);
+  BOOST_WARN_EQUAL(map.total_depth(), 8u);
+  BOOST_REQUIRE_EQUAL(map.size(), 4u);
+  BOOST_REQUIRE_EQUAL(map.optimal_depth(), 3u);
+  BOOST_REQUIRE_THROW(map.insert(k4), ValueExistsException);
+  BOOST_REQUIRE(map.find(k1));
+  BOOST_REQUIRE_EQUAL(*map.find(k1), k1);
+  BOOST_REQUIRE(map.find(k2));
+  BOOST_REQUIRE_EQUAL(*map.find(k2), k2);
+  BOOST_REQUIRE(map.find(k4));
+  BOOST_REQUIRE_EQUAL(*map.find(k4), k4);
+}
--- a/ThreadSafeLookup.hpp	Sat Aug 25 01:42:13 2012 +0200
+++ b/ThreadSafeLookup.hpp	Tue Aug 28 18:58:02 2012 +0200
@@ -31,10 +31,10 @@
   typedef QHash<Key_t, Value_t> map_t;
   map_t map;
 
-  typename Locking<isLocking>::Lock_t masterLock;
+  mutable typename Locking<isLocking>::Lock_t masterLock;
 public:
 
-  boost::optional<Value_t> value(const Key_t& key)
+  boost::optional<Value_t> value(const Key_t& key) const
   {
     boost::optional<Value_t> retVal;
     typename Locking<isLocking>::Locker_t lock(&masterLock);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UniqueString.cpp	Tue Aug 28 18:58:02 2012 +0200
@@ -0,0 +1,4 @@
+#include "UniqueString.hpp"
+
+//QMap<QString, QString> UniqueString::map;
+RBTree<QString> UniqueString::map;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UniqueString.hpp	Tue Aug 28 18:58:02 2012 +0200
@@ -0,0 +1,47 @@
+#ifndef UNIQUESTRING_HPP
+#define UNIQUESTRING_HPP
+
+#if 0
+#include <QtCore/QMap>
+#include <QtCore/QString>
+
+class UniqueString : public QString
+{
+private:
+  static QMap<QString, QString> map;
+  
+public:
+  UniqueString(const QString& str)
+  {
+    if (!map.contains(str)) {
+      map.insert(str, str);
+    }
+    QString::operator=(map.value(str)); 
+  }
+};
+#else
+#include "RBTree.hpp"
+#include <QtCore/QString>
+#include <QtCore/QDebug>
+
+class UniqueString : public QString
+{
+private:
+  static RBTree<QString> map;
+  
+public:
+  UniqueString(const QString& str)
+  {
+    boost::optional<QString> present = map.find(str);
+    if (!present) {
+      map.insert(str);
+      present = str;
+      //qDebug() << map.optimal_depth() << " : " << map.avg_depth() << " : " << map.depth();
+    }
+    QString::operator=(*present);
+
+  }
+};
+#endif
+
+#endif //UNIQUESTRING_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ValueExistsException.hpp	Tue Aug 28 18:58:02 2012 +0200
@@ -0,0 +1,10 @@
+#ifndef VALUEEXISTSEXCEPTION_HPP
+#define VALUEEXISTSEXCEPTION_HPP
+
+#include "Exception.hpp"
+
+class ValueExistsException : public Exception
+{
+};
+
+#endif //VALUEEXISTSEXCEPTION_HPP