Blob Blame History Raw
--- git.orig/admin/build/build_package.sh
+++ git/admin/build/build_package.sh
@@ -82,10 +82,13 @@ build_package_psi() {
 		cp $mqtdir/bin/QtNetwork4.dll $arch_prefix
 		cp $mqtdir/bin/QtXml4.dll $arch_prefix
 		cp $mqtdir/bin/QtGui4.dll $arch_prefix
+		cp $mqtdir/bin/QtSql4.dll $arch_prefix
 		mkdir -p $arch_prefix/imageformats
 		cp $mqtdir/plugins/imageformats/qgif4.dll $arch_prefix/imageformats
 		cp $mqtdir/plugins/imageformats/qjpeg4.dll $arch_prefix/imageformats
 		cp $mqtdir/plugins/imageformats/qmng4.dll $arch_prefix/imageformats
+		mkdir -p $arch_prefix/sqldrivers
+		cp $mqtdir/plugins/sqldrivers/qsqlite4.dll
 		cp $deps_base/$qca_win_dir/$target_arch/bin/qca2.dll $arch_prefix
 		mkdir -p $arch_prefix/crypto
 		cp $deps_base/$qca_win_dir/$target_arch/plugins/crypto/qca-gnupg2.dll $arch_prefix/crypto
@@ -126,8 +129,8 @@ build_package_psi() {
 			QT_LIB_PATH=$QTDIR/lib
 		fi
 		cd $psi_base
-		export DYLD_FRAMEWORK_PATH=$QT_LIB_PATH:$deps_base/$qca_mac_dir/lib:$deps_base/$growl_dir/Framework
-		./configure --with-qca-inc=$deps_base/$qca_mac_dir/include --with-qca-lib=$deps_base/$qca_mac_dir/lib --with-growl=$deps_base/$growl_dir/Framework --enable-universal
+		export DYLD_FRAMEWORK_PATH=$QT_LIB_PATH:$deps_base/$qca_mac_dir/lib:$deps_base/$growl_dir/Framework:$deps_base/$qjson_mac_dir/lib
+		./configure --with-qca-inc=$deps_base/$qca_mac_dir/include --with-qca-lib=$deps_base/$qca_mac_dir/lib --with-growl=$deps_base/$growl_dir/Framework --enable-universal --with-qjson-lib=$deps_base/$qjson_mac_dir/lib
 		make
 	fi
 }
--- git.orig/admin/build/devconfig.sh
+++ git/admin/build/devconfig.sh
@@ -60,7 +60,7 @@ if [ "$platform" == "win" ]; then
 	fi
 	mqtdir=`get_msys_path $qtdir`
 
-	PATH=$mqtdir/bin:$PATH ./configure.exe --qtdir=$qtdir --release --with-qca-inc=$deps_base/$qca_win_dir/$target_arch/include --with-qca-lib=$deps_base/$qca_win_dir/$target_arch/lib --with-zlib-inc=$deps_base/$zlib_win_dir/$target_arch/include --with-zlib-lib=$deps_base/$zlib_win_dir/$target_arch/lib --with-aspell-inc=$deps_base/$aspell_win_dir/$target_arch/include --with-aspell-lib=$deps_base/$aspell_win_dir/$target_arch/lib
+	PATH=$mqtdir/bin:$PATH ./configure.exe --qtdir=$qtdir --release --with-qca-inc=$deps_base/$qca_win_dir/$target_arch/include --with-qca-lib=$deps_base/$qca_win_dir/$target_arch/lib --with-zlib-inc=$deps_base/$zlib_win_dir/$target_arch/include --with-zlib-lib=$deps_base/$zlib_win_dir/$target_arch/lib --with-aspell-inc=$deps_base/$aspell_win_dir/$target_arch/include --with-aspell-lib=$deps_base/$aspell_win_dir/$target_arch/lib --with-qjson-inc=$deps_base/$qjson_win_dir/$target_arch/include --with-qjson-lib=$deps_base/$qjson_win_dir/$target_arch/lib
 
 	rm -f $build_base/devenv
 	touch $build_base/devenv
@@ -79,8 +79,8 @@ else
 	if [ "$QT_PLUGIN_PATH" == "" ]; then
 		QT_PLUGIN_PATH=$QTDIR/plugins
 	fi
-	export DYLD_FRAMEWORK_PATH=$QT_LIB_PATH:$deps_base/$qca_mac_dir/lib:$deps_base/$growl_dir/Framework
-	./configure --with-qca-inc=$deps_base/$qca_mac_dir/include --with-qca-lib=$deps_base/$qca_mac_dir/lib --with-growl=$deps_base/$growl_dir/Framework --enable-universal
+	export DYLD_FRAMEWORK_PATH=$QT_LIB_PATH:$deps_base/$qca_mac_dir/lib:$deps_base/$growl_dir/Framework:$deps_base/$qjson_mac_dir/lib
+	./configure --with-qca-inc=$deps_base/$qca_mac_dir/include --with-qca-lib=$deps_base/$qca_mac_dir/lib --with-growl=$deps_base/$growl_dir/Framework --enable-universal --with-qjson-lib=$deps_base/$qjson_mac_dir/lib
 
 	# remove some gstbundle problem files
 	rm -f $deps_base/$gstbundle_mac_dir/uni/lib/gstreamer-0.10/libgstximagesink.so
@@ -91,7 +91,7 @@ else
 	rm -f $build_base/devenv
 	touch $build_base/devenv
 	echo "export DYLD_LIBRARY_PATH=$deps_base/$gstbundle_mac_dir/uni/lib:\$DYLD_LIBRARY_PATH" >> $build_base/devenv
-	echo "export DYLD_FRAMEWORK_PATH=$QT_LIB_PATH:$deps_base/$qca_mac_dir/lib:$deps_base/$growl_dir/Framework:\$DYLD_FRAMEWORK_PATH" >> $build_base/devenv
+	echo "export DYLD_FRAMEWORK_PATH=$QT_LIB_PATH:$deps_base/$qca_mac_dir/lib:$deps_base/$growl_dir/Framework:$deps_base/$qjson_mac_dir/lib:\$DYLD_FRAMEWORK_PATH" >> $build_base/devenv
 	echo "export GST_PLUGIN_PATH=$deps_base/$gstbundle_mac_dir/uni/lib/gstreamer-0.10" >> $build_base/devenv
 	echo "export GST_REGISTRY_FORK=no" >> $build_base/devenv
 	echo "export QT_PLUGIN_PATH=$QT_PLUGIN_PATH:$deps_base/$qca_mac_dir/plugins" >> $build_base/devenv
--- git.orig/admin/build/package_info
+++ git/admin/build/package_info
@@ -39,3 +39,11 @@ psimedia_win_dir=psimedia-20120725-win
 psimedia_mac_file=psimedia-20120725-mac.tar.bz2
 psimedia_mac_url=http://psi-im.org/files/deps/psimedia-20120725-mac.tar.bz2
 psimedia_mac_dir=psimedia-20120725-mac
+
+qjson_win_file=qjson-0.8.1-win.zip
+qjson_win_url=http://psi-im.org/files/deps/qjson-0.8.1-win.zip
+qjson_win_dir=qjson-0.8.1-win
+
+qjson_mac_file=qjson-0.8.1-mac.tar.bz2
+qjson_mac_url=http://psi-im.org/files/deps/qjson-0.8.1-mac.tar.bz2
+qjson_mac_dir=qjson-0.8.1-mac
--- git.orig/admin/build/prep_dist.sh
+++ git/admin/build/prep_dist.sh
@@ -45,8 +45,8 @@ if [ "$platform" == "mac" ]; then
 
 	mkdir -p $target_dist_base
 
-	QT_FRAMEWORKS="QtCore QtNetwork QtXml QtGui"
-	QT_PLUGINS="imageformats/libqjpeg.dylib imageformats/libqgif.dylib imageformats/libqmng.dylib"
+	QT_FRAMEWORKS="QtCore QtNetwork QtXml QtGui QtSql"
+	QT_PLUGINS="imageformats/libqjpeg.dylib imageformats/libqgif.dylib imageformats/libqmng.dylib sqldrivers/libqsqlite.dylib"
 	QCA_PLUGINS="crypto/libqca-ossl.dylib crypto/libqca-gnupg.dylib"
 
 	cp -a $psi_base/psi.app $target_dist_base/Psi.app
@@ -57,6 +57,7 @@ if [ "$platform" == "mac" ]; then
 	done
 
 	install_name_tool -change qca.framework/Versions/2/qca @executable_path/../Frameworks/qca.framework/Versions/2/qca $contentsdir/MacOS/psi
+	install_name_tool -change qjson.framework/Versions/0.8.1/qjson @executable_path/../Frameworks/qjson.framework/Versions/0.8.1/qjson $contentsdir/MacOS/psi
 
 	mkdir -p $contentsdir/Frameworks
 	for f in $QT_FRAMEWORKS; do
@@ -80,8 +81,14 @@ if [ "$platform" == "mac" ]; then
 	cp -a $deps_base/$qca_mac_dir/lib/qca.framework $contentsdir/Frameworks
 	cleanup_framework $contentsdir/Frameworks/qca.framework qca 2
 	install_name_tool -id @executable_path/../Frameworks/qca.framework/Versions/2/qca $contentsdir/Frameworks/qca.framework/qca
+
+	cp -a $deps_base/$qjson_mac_dir/lib/qjson.framework $contentsdir/Frameworks
+	cleanup_framework $contentsdir/Frameworks/qjson.framework qjson 0.8.1
+	install_name_tool -id @executable_path/../Frameworks/qjson.framework/Versions/0.8.1/qjson $contentsdir/Frameworks/qjson.framework/qjson
+
 	for g in $QT_FRAMEWORKS; do
 		install_name_tool -change $g.framework/Versions/4/$g @executable_path/../Frameworks/$g.framework/Versions/4/$g $contentsdir/Frameworks/qca.framework/qca
+		install_name_tool -change $g.framework/Versions/4/$g @executable_path/../Frameworks/$g.framework/Versions/4/$g $contentsdir/Frameworks/qjson.framework/qjson
 	done
 
 	mkdir -p $contentsdir/Plugins/crypto
--- git.orig/options/default.xml
+++ git/options/default.xml
@@ -132,6 +132,9 @@
 				<chatedit-height type="int">10</chatedit-height>
 				<default-jid-mode comment="Default jid mode: barejid | auto" type="QString">auto</default-jid-mode>
 				<default-jid-mode-ignorelist comment="Default autojid mode ignore list: jid1,jid2,..." type="QString"></default-jid-mode-ignorelist>
+				<history comment="Message history options">
+					<preload-history-size comment="The number of preloaded messages" type="int">5</preload-history-size>
+				</history>
 			</chat>
 			<save>
 				<toolbars-state type="QByteArray"/>
@@ -769,6 +772,9 @@ QLineEdit#le_status_text {
 			</devices>
 			<video-support type="bool">false</video-support>
 		</media>
+		<history comment="General history options">
+			<store-muc-private comment="Keep a history of correspondence for MUC private" type="bool">false</store-muc-private>
+		</history>
 	</options>
 	<accounts comment="Account definitions and options"/>
 	<plugins comment="Plugin options"/>
--- git.orig/psi.qc
+++ git/psi.qc
@@ -17,6 +17,9 @@
   <dep type='qca'>
     <required/>
   </dep>
+  <dep type='qjson'>
+    <required/>
+  </dep>
   <dep type='zlib'>
     <required/>
   </dep>
--- git.orig/qcm/qjson.qcm
+++ git/qcm/qjson.qcm
@@ -0,0 +1,166 @@
+/*
+-----BEGIN QCMOD-----
+name: QJson
+arg: with-qjson-inc=[path],Path to QJson include files
+arg: with-qjson-lib=[path],Path to QJson library or framework files
+-----END QCMOD-----
+*/
+
+// adapted from json.prf
+static QString internal_json_prf(const QString &incdir, const QString &libdir, const QString &frameworkdir)
+{
+	QString out = QString(
+"QJSON_INCDIR = %1\n"
+"QJSON_LIBDIR = %2\n"
+"QJSON_FRAMEWORKDIR = %3\n"
+"\n"
+"CONFIG *= qt\n"
+"\n"
+"LINKAGE =\n"
+"\n"
+"!isEmpty(QJSON_FRAMEWORKDIR): {\n"
+"	framework_dir = $$QJSON_FRAMEWORKDIR\n"
+"	exists($$framework_dir/qjson.framework) {\n"
+"		QMAKE_FRAMEWORKPATH *= $$framework_dir\n"
+"		LIBS *= -F$$framework_dir\n"
+"		INCLUDEPATH += $$framework_dir/qjson.framework\n"
+"		LINKAGE = -framework qjson\n"
+"	}\n"
+"}\n"
+"\n"
+"# else, link normally\n"
+"isEmpty(LINKAGE) {\n"
+"	!isEmpty(QJSON_INCDIR):INCLUDEPATH += $$QJSON_INCDIR\n"
+"	!isEmpty(QJSON_LIBDIR):LIBS += -L$$QJSON_LIBDIR\n"
+"	LINKAGE = -lqjson\n"
+"	CONFIG(debug, debug|release) {\n"
+"		windows:LINKAGE = -lqjsond\n"
+"		mac:LINKAGE = -lqjson_debug\n"
+"	}\n"
+"}\n"
+"\n"
+"LIBS += $$LINKAGE\n"
+	).arg(incdir, libdir, frameworkdir);
+	return out;
+}
+
+// set either libdir or frameworkdir, but not both
+static bool qjson_try(Conf *conf, const QString &incdir, const QString &libdir, const QString &frameworkdir, bool release, bool debug, QString *_prf)
+{
+	QString proextra;
+	QString prf = internal_json_prf(incdir, libdir, frameworkdir);
+	proextra =
+	"CONFIG += qt\n"
+	"CONFIG -= debug_and_release debug release\n"
+	"QT -= gui\n";
+	proextra += prf;
+
+	QString str =
+	"#include <qjson/parser.h>\n"
+	"\n"
+	"int main()\n"
+	"{\n"
+	"	return 0;\n"
+	"}\n";
+
+	// test desired versions, potentially both release and debug
+
+	if(release)
+	{
+		int ret;
+		if(!conf->doCompileAndLink(str, QStringList(), QString(), proextra + "CONFIG += release\n", &ret) || ret != 0)
+			return false;
+	}
+
+	if(debug)
+	{
+		int ret;
+		if(!conf->doCompileAndLink(str, QStringList(), QString(), proextra + "CONFIG += debug\n", &ret) || ret != 0)
+			return false;
+	}
+
+	*_prf = prf;
+	return true;
+}
+
+static bool qjson_try_lib(Conf *conf, const QString &incdir, const QString &libdir, bool release, bool debug, QString *prf)
+{
+	return qjson_try(conf, incdir, libdir, QString(), release, debug, prf);
+}
+
+static bool qjson_try_framework(Conf *conf, const QString &frameworkdir, bool release, bool debug, QString *prf)
+{
+	return qjson_try(conf, QString(), QString(), frameworkdir, release, debug, prf);
+}
+
+//----------------------------------------------------------------------------
+// qc_qjson
+//----------------------------------------------------------------------------
+class qc_qjson : public ConfObj
+{
+public:
+	qc_qjson(Conf *c) : ConfObj(c) {}
+	QString name() const { return "QJson"; }
+	QString shortname() const { return "qjson"; }
+	bool exec()
+	{
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+		// get the build mode
+#ifdef QC_BUILDMODE
+		bool release = qc_buildmode_release;
+		bool debug = qc_buildmode_debug;
+#else
+		// else, default to just release mode
+		bool release = true;
+		bool debug = false;
+#endif
+
+		QString qjson_incdir, qjson_libdir, qjson_json_prf;
+		qjson_incdir = conf->getenv("QC_WITH_QJSON_INC");
+		qjson_libdir = conf->getenv("QC_WITH_QJSON_LIB");
+
+#if defined(Q_OS_MAC)
+		if(!qjson_libdir.isEmpty() && qjson_try_framework(conf, qjson_libdir, release, debug, &qjson_json_prf))
+		{
+			conf->addExtra(qjson_json_prf);
+			return true;
+		}
+#endif
+
+		if(!qjson_incdir.isEmpty() && !qjson_libdir.isEmpty() && qjson_try_lib(conf, qjson_incdir, qjson_libdir, release, debug, &qjson_json_prf))
+		{
+			conf->addExtra(qjson_json_prf);
+			return true;
+		}
+
+		QStringList incs;
+		QString version, libs, other;
+		if(conf->findPkgConfig("QJson", VersionAny, QString(), &version, &incs, &libs, &other))
+		{
+			for(int n = 0; n < incs.count(); ++n)
+				conf->addIncludePath(incs[n]);
+			if(!libs.isEmpty())
+				conf->addLib(libs);
+			return true;
+		}
+
+		QStringList prefixes;
+		prefixes += "/usr";
+		prefixes += "/usr/local";
+
+		for(int n = 0; n < prefixes.count(); ++n)
+		{
+			const QString &prefix = prefixes[n];
+			if(qjson_try_lib(conf, prefix + "/include", prefix + "/lib", release, debug, &qjson_json_prf))
+			{
+				conf->addExtra(qjson_json_prf);
+				return true;
+			}
+		}
+
+		return false;
+#else
+		return true;
+#endif
+	}
+};
--- git.orig/src/chatdlg.cpp
+++ git/src/chatdlg.cpp
@@ -73,8 +73,8 @@
 #include "psicontactlist.h"
 #include "accountlabel.h"
 #include "psirichtext.h"
-#include "messageview.h"
 #include "chatview.h"
+#include "eventdb.h"
 
 #ifdef Q_OS_WIN
 #include <windows.h>
@@ -98,6 +98,7 @@ ChatDlg* ChatDlg::create(const Jid& jid,
 ChatDlg::ChatDlg(const Jid& jid, PsiAccount* pa, TabManager* tabManager)
 	: TabbableWidget(jid, pa, tabManager)
 	, highlightersInstalled_(false)
+	, delayedMessages(0)
 {
 	pending_ = 0;
 	keepOpen_ = false;
@@ -110,6 +111,9 @@ ChatDlg::ChatDlg(const Jid& jid, PsiAcco
 
 	status_ = -1;
 
+	historyState = false;
+	preloadHistory();
+
 	autoSelectContact_ = false;
 	if (PsiOptions::instance()->getOption("options.ui.chat.default-jid-mode").toString() == "auto") {
 		UserListItem *uli = account()->findFirstRelevant(jid);
@@ -168,6 +172,8 @@ void ChatDlg::init()
 
 ChatDlg::~ChatDlg()
 {
+	if (delayedMessages)
+		delete delayedMessages;
 	account()->dialogUnregister(this);
 }
 
@@ -433,6 +439,43 @@ UserStatus ChatDlg::userStatusFor(const
 	return u;
 }
 
+void ChatDlg::preloadHistory()
+{
+	int cnt =PsiOptions::instance()->getOption("options.ui.chat.history.preload-history-size").toInt();
+	if (cnt > 0) {
+		holdMessages(true);
+		if (cnt > 100)
+			cnt = 100;
+		EDBHandle *h = new EDBHandle(account()->edb());
+		connect(h, SIGNAL(finished()), this, SLOT(getHistory()));
+		Jid j = jid();
+		if (!account()->findGCContact(j))
+			j = jid().bare();
+		int start = account()->eventQueue()->count(jid(), false);
+		h->get(account()->id(), j, QDateTime(), EDB::Backward, start, cnt);
+	}
+}
+
+void ChatDlg::getHistory()
+{
+	EDBHandle *h = qobject_cast<EDBHandle *>(sender());
+	if (!h)
+		return;
+
+	historyState = true;
+	const EDBResult &r = h->result();
+	for (int i = r.count() - 1; i >= 0; --i) {
+		const EDBItemPtr &item = r.at(i);
+		PsiEvent::Ptr e = item->event();
+		if (e->type() == PsiEvent::Message) {
+			MessageEvent::Ptr me = e.staticCast<MessageEvent>();
+			appendMessage(me->message(), me->originLocal());
+		}
+	}
+	delete h;
+	holdMessages(false);
+}
+
 void ChatDlg::ensureTabbedCorrectly()
 {
 	TabbableWidget::ensureTabbedCorrectly();
@@ -494,7 +537,7 @@ void ChatDlg::updateContact(const Jid &j
 			if (PsiOptions::instance()->getOption("options.ui.chat.show-status-changes").toBool()
 				&& fromPresence && statusChanged)
 			{
-				chatView()->dispatchMessage(MessageView::statusMessage(
+				dispatchMessage(MessageView::statusMessage(
 												dispNick_, status_,
 												statusString_, priority_));
 			}
@@ -789,6 +832,7 @@ void ChatDlg::doSend()
 
 void ChatDlg::doneSend()
 {
+	historyState = false;
 	appendMessage(m_, true);
 	disconnect(chatEdit(), SIGNAL(textChanged()), this, SLOT(setComposing()));
 	chatEdit()->clear();
@@ -817,6 +861,7 @@ void ChatDlg::encryptedMessageSent(int x
 
 void ChatDlg::incomingMessage(const Message &m)
 {
+	historyState = false;
 	if (m.body().isEmpty() && m.subject().isEmpty() && m.urlList().isEmpty()) {
 		// Event message
 		if (m.containsEvent(CancelEvent)) {
@@ -877,14 +922,16 @@ void ChatDlg::appendMessage(const Messag
 	// figure out the encryption state
 	bool encChanged = false;
 	bool encEnabled = false;
-	if (lastWasEncrypted_ != m.wasEncrypted()) {
-		encChanged = true;
+	if (!historyState) {
+		if (lastWasEncrypted_ != m.wasEncrypted()) {
+			encChanged = true;
+		}
+		lastWasEncrypted_ = m.wasEncrypted();
+		encEnabled = lastWasEncrypted_;
 	}
-	lastWasEncrypted_ = m.wasEncrypted();
-	encEnabled = lastWasEncrypted_;
 
 	if (encChanged) {
-		chatView()->dispatchMessage(MessageView::fromHtml(
+		dispatchMessage(MessageView::fromHtml(
 				encEnabled? QString("<icon name=\"psi/cryptoYes\"> ") + tr("Encryption Enabled"):
 							QString("<icon name=\"psi/cryptoNo\"> ") + tr("Encryption Disabled"),
 				MessageView::System
@@ -900,7 +947,9 @@ void ChatDlg::appendMessage(const Messag
 	}
 
 	if (!m.subject().isEmpty()) {
-		chatView()->dispatchMessage(MessageView::subjectMessage(m.subject()));
+		MessageView smv = MessageView::subjectMessage(m.subject());
+		smv.setSpooled(historyState);
+		dispatchMessage(smv);
 	}
 
 	MessageView mv(MessageView::Message);
@@ -930,10 +979,11 @@ void ChatDlg::appendMessage(const Messag
 	mv.setNick(whoNick(local));
 	mv.setUserId(local?account()->jid().full():jid().full()); // theoretically, this can be inferred from the chat dialog properties
 	mv.setDateTime(m.timeStamp());
-	mv.setSpooled(m.spooled());
+	mv.setSpooled(historyState);
 	mv.setAwaitingReceipt(local && m.messageReceipt() == ReceiptRequest);
 	mv.setReplaceId(m.replaceId());
-	chatView()->dispatchMessage(mv);
+	mv.setCarbonDirection(m.carbonDirection());
+	dispatchMessage(mv);
 
 	if (!m.urlList().isEmpty()) {
 		UrlList urls = m.urlList();
@@ -943,12 +993,52 @@ void ChatDlg::appendMessage(const Messag
 		}
 		// Some XMPP clients send links to HTTP uploaded files both in body and in jabber:x:oob.
 		// It's convenient to show only body if OOB data brings no additional information.
-		if (!(urlsMap.size() == 1 && urlsMap.contains(body) && urlsMap.value(body).isEmpty()))
-			chatView()->dispatchMessage(MessageView::urlsMessage(urlsMap));
+		if (!(urlsMap.size() == 1 && urlsMap.contains(body) && urlsMap.value(body).isEmpty())) {
+			MessageView umv = MessageView::urlsMessage(urlsMap);
+			umv.setSpooled(historyState);
+			dispatchMessage(umv);
+		}
 	}
+	emit messageAppended(body, chatView()->textWidget());
+}
+
+void ChatDlg::holdMessages(bool hold)
+{
+	if (hold) {
+		if (!delayedMessages)
+			delayedMessages = new QList<MessageView>();
+	}
+	else if (delayedMessages) {
+		foreach (const MessageView &mv, *delayedMessages)
+		{
+			if (mv.isSpooled())
+				displayMessage(mv);
+		}
+		foreach (const MessageView &mv, *delayedMessages)
+		{
+			if (!mv.isSpooled())
+				displayMessage(mv);
+		}
+		delete delayedMessages;
+		delayedMessages = 0;
+	}
+}
+
+void ChatDlg::dispatchMessage(const MessageView &mv)
+{
+	if (delayedMessages)
+		delayedMessages->append(mv);
+	else
+		displayMessage(mv);
+}
+
+void ChatDlg::displayMessage(const MessageView &mv)
+{
+	chatView()->dispatchMessage(mv);
 
 	// if we're not active, notify the user by changing the title
-	if (!isActiveTab() && m.carbonDirection() != Message::Sent) {
+	MessageView::Type type = mv.type();
+	if (type != MessageView::System && type != MessageView::Status && !mv.isSpooled() && !isActiveTab() && mv.carbonDirection() != Message::Sent) {
 		++pending_;
 		invalidateTab();
 		if (PsiOptions::instance()->getOption("options.ui.flash-windows").toBool()) {
@@ -970,11 +1060,10 @@ void ChatDlg::appendMessage(const Messag
 	//	messagesRead(jid());
 	//}
 
-	if (!local) {
+	if (!mv.isLocal()) {
 		keepOpen_ = true;
 		QTimer::singleShot(1000, this, SLOT(setKeepOpenFalse()));
 	}
-	emit messageAppended(body, chatView()->textWidget());
 }
 
 void ChatDlg::updateIsComposing(bool b)
--- git.orig/src/chatdlg.h
+++ git/src/chatdlg.h
@@ -33,6 +33,7 @@
 #include "advwidget.h"
 
 #include "tabbablewidget.h"
+#include "messageview.h"
 
 
 namespace XMPP
@@ -88,6 +89,7 @@ public:
 	Jid realJid() const;
 	bool autoSelectContact() const {return autoSelectContact_;};
 	static UserStatus userStatusFor(const Jid& jid, QList<UserListItem*> ul, bool forceEmptyResource);
+	void preloadHistory();
 
 signals:
 	void aInfo(const Jid &);
@@ -148,6 +150,7 @@ private slots:
 	void addEmoticon(QString text);
 	void initComposing();
 	void setComposing();
+	void getHistory();
 
 protected slots:
 	void checkComposing();
@@ -159,6 +162,9 @@ protected:
 	void updateRealJid();
 	void resetComposing();
 	void doneSend();
+	void holdMessages(bool hold);
+	void dispatchMessage(const MessageView &mv);
+	void displayMessage(const MessageView &mv);
 	virtual void setLooks();
 	void setSelfDestruct(int);
 	virtual void chatEditCreated();
@@ -214,9 +220,11 @@ private:
 	QTimer* composingTimer_;
 	bool isComposing_;
 	bool sendComposingEvents_;
+	bool historyState;
 	QString eventId_;
 	ChatState contactChatState_;
 	ChatState lastChatState_;
+	QList<MessageView> *delayedMessages;
 };
 
 #endif
--- git.orig/src/chatview_te.cpp
+++ git/src/chatview_te.cpp
@@ -88,6 +88,7 @@ ChatView::ChatView(QWidget *parent)
 			logIconTime = IconsetFactory::iconPixmap("psi/notification_chat_time").scaledToHeight(logIconsSize, Qt::SmoothTransformation);
 			logIconInfo = IconsetFactory::iconPixmap("psi/notification_chat_info").scaledToHeight(logIconsSize, Qt::SmoothTransformation);
 			logIconCorrected = IconsetFactory::iconPixmap("psi/action_templates_edit").scaledToHeight(logIconsSize, Qt::SmoothTransformation);
+			logIconHistory = IconsetFactory::iconPixmap("psi/history").scaledToHeight(logIconsSize, Qt::SmoothTransformation);
 		} else {
 			logIconReceive = IconsetFactory::iconPixmap("psi/notification_chat_receive");
 			logIconSend = IconsetFactory::iconPixmap("psi/notification_chat_send");
@@ -98,6 +99,7 @@ ChatView::ChatView(QWidget *parent)
 			logIconTime = IconsetFactory::iconPixmap("psi/notification_chat_time");
 			logIconInfo = IconsetFactory::iconPixmap("psi/notification_chat_info");
 			logIconCorrected = IconsetFactory::iconPixmap("psi/action_templates_edit");
+			logIconHistory = IconsetFactory::iconPixmap("psi/history");
 		}
 		addLogIconsResources();
 	}
@@ -177,6 +179,7 @@ void ChatView::addLogIconsResources()
 	document()->addResource(QTextDocument::ImageResource, QUrl("icon:log_icon_delivered"), logIconDelivered);
 	document()->addResource(QTextDocument::ImageResource, QUrl("icon:log_icon_delivered_pgp"), logIconDeliveredPgp);
 	document()->addResource(QTextDocument::ImageResource, QUrl("icon:log_icon_corrected"), logIconCorrected);
+	document()->addResource(QTextDocument::ImageResource, QUrl("icon:log_icon_history"), logIconHistory);
 }
 
 void ChatView::markReceived(QString id)
@@ -459,28 +462,49 @@ void ChatView::renderMucMessage(const Me
 void ChatView::renderMessage(const MessageView &mv)
 {
 	QString timestr = formatTimeStamp(mv.dateTime());
-	QString color = colorString(mv.isLocal(), mv.isSpooled());
+	QString color = colorString(mv.isLocal(), false);
 	if (useMessageIcons_ && mv.isAwaitingReceipt()) {
 		document()->addResource(QTextDocument::ImageResource, QUrl(QString("icon:delivery") + mv.messageId()),
 					isEncryptionEnabled_ ? logIconSendPgp : logIconSend);
 	}
-	QString icon = useMessageIcons_ ?
-		(QString("<img src=\"%1\" />").arg(mv.isLocal()?
-		(mv.isAwaitingReceipt() ? QString("icon:delivery") + mv.messageId()
-			: isEncryptionEnabled_ ? "icon:log_icon_send_pgp" : "icon:log_icon_send")
-		: isEncryptionEnabled_ ? "icon:log_icon_receive_pgp" : "icon:log_icon_receive")) : "";
+	QString icon;
+	if (useMessageIcons_) {
+		QString sRes;
+		if (mv.isSpooled())
+			sRes = "icon:log_icon_history";
+		else if (mv.isLocal()) {
+			if (mv.isAwaitingReceipt())
+				sRes = QString("icon:delivery") + mv.messageId();
+			else if (isEncryptionEnabled_)
+				sRes = "icon:log_icon_receive_pgp";
+			else
+				sRes = "icon:log_icon_send";
+		} else {
+			if (isEncryptionEnabled_)
+				sRes = "icon:log_icon_receive_pgp";
+			else
+				sRes = "icon:log_icon_receive";
+		}
+		icon = QString("<img src=\"%1\" />").arg(sRes);
+	}
+	QString str;
 
 	QString inner = mv.formattedText() + replaceMarker(mv);
 	if (mv.isEmote()) {
-		appendText(icon + QString("<span style=\"color: %1\">").arg(color) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(TextUtil::escape(mv.nick())) + inner + "</span>");
+		str = icon + QString("<span style=\"color: %1\">").arg(color) + QString("[%1]").arg(timestr) + QString(" *%1 ").arg(TextUtil::escape(mv.nick())) + inner + "</span>";
 	} else {
 		if (PsiOptions::instance()->getOption("options.ui.chat.use-chat-says-style").toBool()) {
-			appendText(icon + QString("<span style=\"color: %1\">").arg(color) + QString("[%1] ").arg(timestr) + tr("%1 says:").arg(TextUtil::escape(mv.nick())) + "</span><br>" + inner);
+			str = icon + QString("<span style=\"color: %1\">").arg(color) + QString("[%1] ").arg(timestr) + tr("%1 says:").arg(TextUtil::escape(mv.nick())) + "</span><br>";
 		}
 		else {
-			appendText(icon + QString("<span style=\"color: %1\">").arg(color) + QString("[%1] &lt;").arg(timestr) + TextUtil::escape(mv.nick()) + QString("&gt;</span> ") + inner);
+			str = icon + QString("<span style=\"color: %1\">").arg(color) + QString("[%1] &lt;").arg(timestr) + TextUtil::escape(mv.nick()) + QString("&gt;</span> ");
 		}
+		if (mv.isSpooled())
+			str.append(QString("<span style=\"color: %1\">%2</span>").arg(ColorOpt::instance()->color("options.ui.look.colors.messages.usertext").name()).arg(inner));
+		else
+			str.append(inner);
 	}
+	appendText(str);
 
 	if (mv.isLocal() && PsiOptions::instance()->getOption("options.ui.chat.auto-scroll-to-bottom").toBool() ) {
 		deferredScroll();
--- git.orig/src/chatview_te.h
+++ git/src/chatview_te.h
@@ -121,6 +121,7 @@ private:
 	QPixmap logIconTime;
 	QPixmap logIconInfo;
 	QPixmap logIconCorrected;
+	QPixmap logIconHistory;
 	QAction *actQuote_;
 };
 
--- git.orig/src/edbflatfile.cpp
+++ git/src/edbflatfile.cpp
@@ -41,25 +41,23 @@ using namespace XMPP;
 //----------------------------------------------------------------------------
 // EDBFlatFile
 //----------------------------------------------------------------------------
+
 struct item_file_req
 {
 	Jid j;
 	int type; // 0 = latest, 1 = oldest, 2 = random, 3 = write
+	int start;
 	int len;
 	int dir;
 	int id;
-	int eventId;
+	QDateTime date;
 	QString findStr;
 	PsiEvent::Ptr event;
 
-	QDateTime first, last;
 	enum Type {
-		Type_getLatest = 0,
-		Type_getOldest,
 		Type_get,
 		Type_append,
 		Type_find,
-		Type_getByDate,
 		Type_erase
 	};
 };
@@ -93,64 +91,23 @@ int EDBFlatFile::features() const
 	return 0;
 }
 
-int EDBFlatFile::getLatest(const Jid &j, int len)
-{
-	item_file_req *r = new item_file_req;
-	r->j = j;
-	r->type = item_file_req::Type_getLatest;
-	r->len = len < 1 ? 1: len;
-	r->id = genUniqueId();
-	d->rlist.append(r);
-
-	QTimer::singleShot(FAKEDELAY, this, SLOT(performRequests()));
-	return r->id;
-}
-
-int EDBFlatFile::getOldest(const Jid &j, int len)
-{
-	item_file_req *r = new item_file_req;
-	r->j = j;
-	r->type = item_file_req::Type_getOldest;
-	r->len = len < 1 ? 1: len;
-	r->id = genUniqueId();
-	d->rlist.append(r);
-
-	QTimer::singleShot(FAKEDELAY, this, SLOT(performRequests()));
-	return r->id;
-}
-
-int EDBFlatFile::get(const Jid &j, const QString &id, int direction, int len)
+int EDBFlatFile::get(const QString &/*accId*/, const Jid &j, const QDateTime date, int direction, int start, int len)
 {
 	item_file_req *r = new item_file_req;
 	r->j = j;
 	r->type = item_file_req::Type_get;
+	r->start = start;
 	r->len = len < 1 ? 1: len;
 	r->dir = direction;
-	r->eventId = id.toInt();
-	r->id = genUniqueId();
-	d->rlist.append(r);
-
-	QTimer::singleShot(FAKEDELAY, this, SLOT(performRequests()));
-	return r->id;
-}
-
-
-int EDBFlatFile::getByDate(const XMPP::Jid &jid, QDateTime first, QDateTime last)
-{
-	item_file_req *r = new item_file_req;
-	r->j = jid;
-	r->type = item_file_req::Type_getByDate;
-	r->len = 1;
+	r->date = date;
 	r->id = genUniqueId();
 	d->rlist.append(r);
-	r->first = first;
-	r->last = last;
 
 	QTimer::singleShot(FAKEDELAY, this, SLOT(performRequests()));
 	return r->id;
 }
 
-int EDBFlatFile::find(const QString &str, const Jid &j, const QString &id, int direction)
+int EDBFlatFile::find(const QString &/*accId*/, const QString &str, const Jid &j, const QDateTime date, int direction)
 {
 	item_file_req *r = new item_file_req;
 	r->j = j;
@@ -158,7 +115,7 @@ int EDBFlatFile::find(const QString &str
 	r->len = 1;
 	r->dir = direction;
 	r->findStr = str;
-	r->eventId = id.toInt();
+	r->date = date;
 	r->id = genUniqueId();
 	d->rlist.append(r);
 
@@ -166,8 +123,10 @@ int EDBFlatFile::find(const QString &str
 	return r->id;
 }
 
-int EDBFlatFile::append(const Jid &j, const PsiEvent::Ptr &e)
+int EDBFlatFile::append(const QString &/*accId*/, const Jid &j, const PsiEvent::Ptr &e, int type)
 {
+	if (type != EDB::Contact)
+		return 0;
 	item_file_req *r = new item_file_req;
 	r->j = j;
 	r->type = item_file_req::Type_append;
@@ -184,7 +143,7 @@ int EDBFlatFile::append(const Jid &j, co
 	return r->id;
 }
 
-int EDBFlatFile::erase(const Jid &j)
+int EDBFlatFile::erase(const QString &/*accId*/, const Jid &j)
 {
 	item_file_req *r = new item_file_req;
 	r->j = j;
@@ -203,6 +162,17 @@ QList<EDB::ContactItem> EDBFlatFile::con
 	return File::contacts(psi()->contactList()->defaultAccount()->id(), type);
 }
 
+quint64 EDBFlatFile::eventsCount(const QString &accId, const XMPP::Jid &jid)
+{
+	quint64 res = 0;
+	if (!jid.isEmpty())
+		res = ensureFile(jid)->total();
+	else
+		foreach (const ContactItem &ci, contacts(accId, Contact))
+			res += ensureFile(ci.jid)->total();
+	return res;
+}
+
 EDBFlatFile::File *EDBFlatFile::findFile(const Jid &j) const
 {
 	foreach(File* i, d->flist) {
@@ -256,26 +226,9 @@ void EDBFlatFile::performRequests()
 
 	File *f = ensureFile(r->j);
 	int type = r->type;
-	if(type >= item_file_req::Type_getLatest && type <= item_file_req::Type_get) {
-		int id, direction;
-
-		if(type == item_file_req::Type_getLatest) {
-			direction = Backward;
-			id = f->total()-1;
-		}
-		else if(type == item_file_req::Type_getOldest) {
-			direction = Forward;
-			id = 0;
-		}
-		else if(type == item_file_req::Type_get) {
-			direction = r->dir;
-			id = r->eventId;
-		}
-		else {
-			qWarning("EDBFlatFile::performRequests(): Invalid type.");
-			return;
-		}
-
+	if(type == item_file_req::Type_get) {
+		int direction = r->dir;
+		int id = f->getId(r->date, direction, r->start);
 		int len;
 		if(direction == Forward) {
 			if(id + r->len > f->total())
@@ -291,90 +244,63 @@ void EDBFlatFile::performRequests()
 		}
 
 		EDBResult result;
-		for(int n = 0; n < len; ++n) {
-			PsiEvent::Ptr e(f->get(id));
-			if(e) {
-				QString prevId, nextId;
-				if(id > 0)
-					prevId = QString::number(id-1);
-				if(id < f->total()-1)
-					nextId = QString::number(id+1);
-				EDBItemPtr ei = EDBItemPtr(new EDBItem(e, QString::number(id), prevId, nextId));
-				result.append(ei);
-			}
+		int startId = 0;
+		if (id != -1) {
+			startId = id;
+			for(int n = 0; n < len; ++n) {
+				PsiEvent::Ptr e(f->get(id));
+				if(e) {
+					EDBItemPtr ei = EDBItemPtr(new EDBItem(e, QString::number(id)));
+					result.append(ei);
+				}
 
-			if(direction == Forward)
-				++id;
-			else
-				--id;
+				if(direction == Forward)
+					++id;
+				else
+					--id;
+			}
+			if (direction == Backward)
+				startId = id + 1;
 		}
-		resultReady(r->id, result);
+		resultReady(r->id, result, startId);
 	}
 	else if(type == item_file_req::Type_append) {
 		writeFinished(r->id, f->append(r->event));
 	}
 	else if(type == item_file_req::Type_find) {
-		int id = r->eventId;
+		int id = f->getId(r->date, r->dir, r->start);
 		EDBResult result;
-		while(1) {
-			PsiEvent::Ptr e(f->get(id));
-			if(!e)
-				break;
+		if (id != -1) {
+			while(1) {
+				PsiEvent::Ptr e(f->get(id));
+				if(!e)
+					break;
 
-			QString prevId, nextId;
-			if(id > 0)
-				prevId = QString::number(id-1);
-			if(id < f->total()-1)
-				nextId = QString::number(id+1);
-
-			if(e->type() == PsiEvent::Message) {
-				MessageEvent::Ptr me = e.staticCast<MessageEvent>();
-				const Message &m = me->message();
-				if(m.body().indexOf(r->findStr, 0, Qt::CaseInsensitive) != -1) {
-					EDBItemPtr ei = EDBItemPtr(new EDBItem(e, QString::number(id), prevId, nextId));
-					result.append(ei);
-					//commented line below to return ALL(instead of just first) messages that contain findStr
-					//break;
+				if(e->type() == PsiEvent::Message) {
+					MessageEvent::Ptr me = e.staticCast<MessageEvent>();
+					const Message &m = me->message();
+					if(m.body().indexOf(r->findStr, 0, Qt::CaseInsensitive) != -1) {
+						EDBItemPtr ei = EDBItemPtr(new EDBItem(e, QString::number(id)));
+						result.append(ei);
+						//commented line below to return ALL(instead of just first) messages that contain findStr
+						//break;
+					}
 				}
-			}
 
-			if(r->dir == Forward)
-				++id;
-			else
-				--id;
-		}
-		resultReady(r->id, result);
-	}
-	else if(type == item_file_req::Type_getByDate ) {
-		EDBResult result;
-		for (int id = f->findNearestDate(r->first); id < f->total(); ++id) {
-			PsiEvent::Ptr e(f->get(id));
-			if(!e)
-				continue;
-
-			QString prevId, nextId;
-			if(id > 0)
-				prevId = QString::number(id-1);
-			if(id < f->total()-1)
-				nextId = QString::number(id+1);
-
-			if(e->type() == PsiEvent::Message) {
-				MessageEvent::Ptr me = e.staticCast<MessageEvent>();
-				const Message &m = me->message();
-				if(m.timeStamp() > r->first) {
-					if (m.timeStamp() >= r->last )
-						break;
-					EDBItemPtr ei = EDBItemPtr(new EDBItem(e, QString::number(id), prevId, nextId));
-					result.append(ei);
-				}
+				if(r->dir == Forward)
+					++id;
+				else
+					--id;
 			}
 		}
-		resultReady(r->id, result);
+		resultReady(r->id, result, 0);
 	}
-
 	else if(type == item_file_req::Type_erase) {
 		writeFinished(r->id, deleteFile(f->j));
 	}
+	else {
+		qWarning("EDBFlatFile::performRequests(): Invalid type.");
+	}
 
 	delete r;
 }
@@ -428,7 +354,7 @@ EDBFlatFile::File::~File()
 
 QString EDBFlatFile::File::jidToFileName(const XMPP::Jid &j)
 {
-	return ApplicationInfo::historyDir() + "/" + JIDUtil::encode(j.bare()).toLower() + ".history";
+	return ApplicationInfo::historyDir() + "/" + strToFileName(JIDUtil::encode(j.bare()).toLower());
 }
 
 QString EDBFlatFile::File::strToFileName(const QString &s)
@@ -501,6 +427,41 @@ int EDBFlatFile::File::total() const
 	return d->index.size();
 }
 
+int EDBFlatFile::File::getId(QDateTime &date, int dir, int offset)
+{
+	if (date.isNull()) {
+		if (dir == EDBFlatFile::Forward)
+			return offset;
+		if (offset >= total())
+			return 0;
+		return total() - offset - 1;
+	}
+	ensureIndex();
+	int id = findNearestDate(date);
+	if (id != -1)
+		return -1;
+
+	QDateTime fDate = getDate(id);
+	if (!fDate.isValid())
+		return -1;
+
+	if (dir == EDBFlatFile::Forward) {
+		if (fDate < date)
+			++id;
+		id += offset;
+	}
+	else {
+		if (fDate > date)
+			--id;
+		id -= offset;
+	}
+	if (id >= total())
+		id = total() - 1;
+	else if (id < 0)
+		id = 0;
+	return id;
+}
+
 /*
  * This method returns an index of a string with the event
  * which has the nearest date to the specified one.
--- git.orig/src/edbflatfile.h
+++ git/src/edbflatfile.h
@@ -38,14 +38,14 @@ public:
 	~EDBFlatFile();
 
 	int features() const;
-	int getLatest(const XMPP::Jid &, int len);
-	int getOldest(const XMPP::Jid &, int len);
-	int get(const XMPP::Jid &jid, const QString &id, int direction, int len);
-	int getByDate(const XMPP::Jid &jid, QDateTime first, QDateTime last);
-	int find(const QString &, const XMPP::Jid &, const QString &id, int direction);
-	int append(const XMPP::Jid &, const PsiEvent::Ptr&);
-	int erase(const XMPP::Jid &);
+	int get(const QString &accId, const XMPP::Jid &jid, const QDateTime date, int direction, int start, int len);
+	int find(const QString &accId, const QString &, const XMPP::Jid &, const QDateTime date, int direction);
+	int append(const QString &accId, const XMPP::Jid &, const PsiEvent::Ptr &, int);
+	int erase(const QString &accId, const XMPP::Jid &);
 	QList<EDB::ContactItem> contacts(const QString &accId, int type);
+	quint64 eventsCount(const QString &accId, const XMPP::Jid &jid);
+	QString getStorageParam(const QString &) { return QString(); }
+	void setStorageParam(const QString &, const QString &) {}
 
 	class File;
 
@@ -70,6 +70,7 @@ public:
 	~File();
 
 	int total() const;
+	int getId(QDateTime &date, int dir, int offset);
 	void touch();
 	PsiEvent::Ptr get(int);
 	bool append(const PsiEvent::Ptr &);
--- git.orig/src/edbsqlite.cpp
+++ git/src/edbsqlite.cpp
@@ -0,0 +1,874 @@
+/*
+ * edbsqlite.cpp
+ * Copyright (C) 2011   Aleksey Andreev
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <QSqlError>
+#include <QSqlDriver>
+#ifndef HAVE_QT5
+#include <qjson/parser.h>
+#include "qjson/serializer.h"
+#else
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#endif
+
+#include "edbsqlite.h"
+//#include "common.h"
+#include "applicationinfo.h"
+#include "psicontactlist.h"
+#include "jidutil.h"
+#include "historyimp.h"
+
+#define FAKEDELAY 0
+
+using namespace XMPP;
+
+//----------------------------------------------------------------------------
+// EDBSqLite
+//----------------------------------------------------------------------------
+
+EDBSqLite::EDBSqLite(PsiCon *psi) : EDB(psi),
+	transactionsCounter(0),
+	lastCommitTime(QDateTime::currentDateTime()),
+	commitTimer(NULL),
+	mirror_(NULL)
+{
+	status = NotActive;
+	QString path = ApplicationInfo::historyDir() + "/history.db";
+	QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "history");
+	db.setDatabaseName(path);
+	if (!db.open()) {
+		qWarning("EDBSqLite::EDBSqLite(): Can't open base.\n" + db.lastError().text().toLatin1());
+		return;
+	}
+	QSqlQuery query(db);
+	query.exec("PRAGMA foreign_keys = ON;");
+	setInsertingMode(Normal);
+	if (db.tables(QSql::Tables).size() == 0) {
+		// no tables found.
+		if (db.transaction()) {
+			query.exec("CREATE TABLE `system` ("
+				"`key` TEXT, "
+				"`value` TEXT"
+				");");
+			query.exec("CREATE TABLE `accounts` ("
+				"`id` TEXT, "
+				"`lifetime` INTEGER"
+				");");
+			query.exec("CREATE TABLE `contacts` ("
+				"`id` INTEGER NOT NULL PRIMARY KEY ASC, "
+				"`acc_id` TEXT, "
+				"`type` INTEGER, "
+				"`jid` TEXT, "
+				"`lifetime` INTEGER"
+				");");
+			query.exec("CREATE TABLE `events` ("
+				"`id` INTEGER NOT NULL PRIMARY KEY ASC, "
+				"`contact_id` INTEGER NOT NULL REFERENCES `contacts`(`id`) ON DELETE CASCADE, "
+				"`resource` TEXT, "
+				"`date` TEXT, "
+				"`type` INTEGER, "
+				"`direction` INTEGER, "
+				"`subject` TEXT, "
+				"`m_text` TEXT, "
+				"`lang` TEXT, "
+				"`extra_data` TEXT"
+				");");
+			query.exec("CREATE INDEX `key` ON `system` (`key`);");
+			query.exec("CREATE INDEX `jid` ON `contacts` (`jid`);");
+			query.exec("CREATE INDEX `contact_id` ON `events` (`contact_id`);");
+			query.exec("CREATE INDEX `date` ON `events` (`date`);");
+			if (db.commit()) {
+				status = Commited;
+				setStorageParam("version", "0.1");
+				setStorageParam("import_start", "yes");
+			}
+		}
+	}
+	else
+		status = Commited;
+}
+
+EDBSqLite::~EDBSqLite()
+{
+	commit();
+	{
+		QSqlDatabase db = QSqlDatabase::database("history", false);
+		if (db.isOpen())
+			db.close();
+	}
+	QSqlDatabase::removeDatabase("history");
+}
+
+bool EDBSqLite::init()
+{
+	if (status == NotActive)
+		return false;
+
+	if (!getStorageParam("import_start").isEmpty()) {
+		if (!importExecute()) {
+			status = NotActive;
+			return false;
+		}
+	}
+
+	setMirror(new EDBFlatFile(psi()));
+	return true;
+}
+
+int EDBSqLite::features() const
+{
+	return SeparateAccounts | PrivateContacts | AllContacts | AllAccounts;
+}
+
+int EDBSqLite::get(const QString &accId, const XMPP::Jid &jid, QDateTime date, int direction, int start, int len)
+{
+	item_query_req *r = new item_query_req;
+	r->accId = accId;
+	r->j     = jid;
+	r->type  = item_query_req::Type_get;
+	r->start = start;
+	r->len   = len < 1 ? 1 : len;
+	r->dir   = direction;
+	r->date  = date;
+	r->id    = genUniqueId();
+	rlist.append(r);
+
+	QTimer::singleShot(FAKEDELAY, this, SLOT(performRequests()));
+	return r->id;
+}
+
+int EDBSqLite::find(const QString &accId, const QString &str, const XMPP::Jid &jid, const QDateTime date, int direction)
+{
+	item_query_req *r = new item_query_req;
+	r->accId   = accId;
+	r->j       = jid;
+	r->type    = item_query_req::Type_find;
+	r->len     = 1;
+	r->dir     = direction;
+	r->findStr = str;
+	r->date    = date;
+	r->id      = genUniqueId();
+	rlist.append(r);
+
+	QTimer::singleShot(FAKEDELAY, this, SLOT(performRequests()));
+	return r->id;
+}
+
+int EDBSqLite::append(const QString &accId, const XMPP::Jid &jid, const PsiEvent::Ptr &e, int type)
+{
+	item_query_req *r = new item_query_req;
+	r->accId   = accId;
+	r->j       = jid;
+	r->jidType = type;
+	r->type    = item_query_req::Type_append;
+	r->event   = e;
+	if ( !r->event ) {
+		qWarning("EDBSqLite::append(): Attempted to append incompatible type.");
+		delete r;
+		return 0;
+	}
+	r->id      = genUniqueId();
+	rlist.append(r);
+
+	QTimer::singleShot(FAKEDELAY, this, SLOT(performRequests()));
+
+	if (mirror_)
+		mirror_->append(accId, jid, e, type);
+
+	return r->id;
+}
+
+int EDBSqLite::erase(const QString &accId, const XMPP::Jid &jid)
+{
+	item_query_req *r = new item_query_req;
+	r->accId = accId;
+	r->j     = jid;
+	r->type  = item_query_req::Type_erase;
+	r->id    = genUniqueId();
+	rlist.append(r);
+
+	QTimer::singleShot(FAKEDELAY, this, SLOT(performRequests()));
+
+	if (mirror_)
+		mirror_->erase(accId, jid);
+
+	return r->id;
+}
+
+QList<EDB::ContactItem> EDBSqLite::contacts(const QString &accId, int type)
+{
+	QList<ContactItem> res;
+	EDBSqLite::PreparedQuery *query = queryes.getPreparedQuery(QueryContactsList, accId.isEmpty(), true);
+	query->bindValue(":type", type);
+	if (!accId.isEmpty())
+		query->bindValue(":acc_id", accId);
+	if (query->exec()) {
+		while (query->next()) {
+			const QSqlRecord &rec = query->record();
+			res.append(ContactItem(rec.value("acc_id").toString(), XMPP::Jid(rec.value("jid").toString())));
+		}
+		query->freeResult();
+	}
+	return res;
+}
+
+quint64 EDBSqLite::eventsCount(const QString &accId, const XMPP::Jid &jid)
+{
+	quint64 res = 0;
+	bool fAccAll  = accId.isEmpty();
+	bool fContAll = jid.isEmpty();
+	EDBSqLite::PreparedQuery *query = queryes.getPreparedQuery(QueryRowCount, fAccAll, fContAll);
+	if (!fAccAll)
+		query->bindValue(":acc_id", accId);
+	if (!fContAll)
+		query->bindValue(":jid", jid.full());
+	if (query->exec()) {
+		if (query->next())
+			res = query->record().value("count").toULongLong();
+		query->freeResult();
+	}
+	return res;
+}
+
+QString EDBSqLite::getStorageParam(const QString &key)
+{
+	QSqlQuery query(QSqlDatabase::database("history"));
+	query.prepare("SELECT `value` FROM `system` WHERE `key` = :key;");
+	query.bindValue(":key", key);
+	if (query.exec() && query.next())
+		return query.record().value("value").toString();
+	return QString();
+}
+
+void EDBSqLite::setStorageParam(const QString &key, const QString &val)
+{
+	transaction(true);
+	QSqlQuery query(QSqlDatabase::database("history"));
+	if (val.isEmpty()) {
+		query.prepare("DELETE FROM `system` WHERE `key` = :key;");
+		query.bindValue(":key", key);
+		query.exec();
+	}
+	else {
+		query.prepare("SELECT COUNT(*) AS `count` FROM `system` WHERE `key` = :key;");
+		query.bindValue(":key", key);
+		if (query.exec() && query.next() && query.record().value("count").toULongLong() != 0) {
+			query.prepare("UPDATE `system` SET `value` = :val WHERE `key` = :key;");
+			query.bindValue(":key", key);
+			query.bindValue(":val", val);
+			query.exec();
+		}
+		else {
+			query.prepare("INSERT INTO `system` (`key`, `value`) VALUES (:key, :val);");
+			query.bindValue(":key", key);
+			query.bindValue(":val", val);
+			query.exec();
+		}
+	}
+	commit();
+}
+
+void EDBSqLite::setInsertingMode(InsertMode mode)
+{
+	// in the case of a flow of new records
+	if (mode == Import) {
+		// Commit after 10000 inserts and every 5 seconds
+		maxUncommitedRecs = 10000;
+		maxUncommitedSecs = 5;
+	} else {
+		// Commit after 3 inserts and every 1 second
+		maxUncommitedRecs = 3;
+		maxUncommitedSecs = 1;
+	}
+	// Commit if there were no new additions for 1 second
+	commitByTimeoutSecs = 1;
+	//--
+	commit();
+}
+
+void EDBSqLite::setMirror(EDBFlatFile *mirr)
+{
+	if (mirr != mirror_) {
+		if (mirror_)
+			delete mirror_;
+		mirror_ = mirr;
+	}
+}
+
+EDBFlatFile *EDBSqLite::mirror() const
+{
+	return mirror_;
+}
+
+void EDBSqLite::performRequests()
+{
+	if (rlist.isEmpty())
+		return;
+
+	item_query_req *r = rlist.takeFirst();
+	const int type = r->type;
+
+	if (type == item_query_req::Type_append) {
+		bool b = appendEvent(r->accId, r->j, r->event, r->jidType);
+		writeFinished(r->id, b);
+	}
+
+	else if (type == item_query_req::Type_get) {
+		commit();
+		bool fContAll = r->j.isEmpty();
+		bool fAccAll  = r->accId.isEmpty();
+		QueryType queryType;
+		if (r->date.isNull()) {
+			if (r->dir == Forward)
+				queryType = QueryOldest;
+			else
+				queryType = QueryLatest;
+		} else {
+			if (r->dir == Backward)
+				queryType = QueryDateBackward;
+			else
+				queryType = QueryDateForward;
+		}
+		EDBSqLite::PreparedQuery *query = queryes.getPreparedQuery(queryType, fAccAll, fContAll);
+		if (!fContAll)
+			query->bindValue(":jid", r->j.full());
+		if (!fAccAll)
+			query->bindValue(":acc_id", r->accId);
+		if (!r->date.isNull())
+			query->bindValue(":date", r->date);
+		query->bindValue(":start", r->start);
+		query->bindValue(":cnt", r->len);
+		EDBResult result;
+		if (query->exec()) {
+			while (query->next()) {
+				PsiEvent::Ptr e(getEvent(query->record()));
+				if (e) {
+					QString id = query->record().value("id").toString();
+					result.append(EDBItemPtr(new EDBItem(e, id)));
+				}
+			}
+			query->freeResult();
+		}
+		int beginRow;
+		if (r->dir == Forward && r->date.isNull()) {
+			beginRow = r->start;
+		} else {
+			int cnt = rowCount(r->accId, r->j, r->date);
+			if (r->dir == Backward) {
+				beginRow = cnt - r->len + 1;
+				if (beginRow < 0)
+					beginRow = 0;
+			} else {
+				beginRow = cnt + 1;
+			}
+		}
+		resultReady(r->id, result, beginRow);
+
+	} else if(type == item_query_req::Type_find) {
+		commit();
+		bool fContAll = r->j.isEmpty();
+		bool fAccAll  = r->accId.isEmpty();
+		EDBSqLite::PreparedQuery *query = queryes.getPreparedQuery(QueryFindText, fAccAll, fContAll);
+		if (!fContAll)
+			query->bindValue(":jid", r->j.full());
+		if (!fAccAll)
+			query->bindValue(":acc_id", r->accId);
+		EDBResult result;
+		if (query->exec()) {
+			QString str = r->findStr.toLower();
+			while (query->next()) {
+				const QSqlRecord rec = query->record();
+				if (!rec.value("m_text").toString().toLower().contains(str, Qt::CaseSensitive))
+					continue;
+				PsiEvent::Ptr e(getEvent(rec));
+				if (e) {
+					QString id = rec.value("id").toString();
+					EDBItemPtr eip = EDBItemPtr(new EDBItem(e, id));
+					result.append(eip);
+				}
+			}
+			query->freeResult();
+		}
+		resultReady(r->id, result, 0);
+
+	} else if(type == item_query_req::Type_erase) {
+		writeFinished(r->id, eraseHistory(r->accId, r->j));
+	}
+
+	delete r;
+}
+
+bool EDBSqLite::appendEvent(const QString &accId, const XMPP::Jid &jid, const PsiEvent::Ptr &e, int jidType)
+{
+	QSqlDatabase db = QSqlDatabase::database("history");
+	const qint64 contactId = ensureJidRowId(accId, jid, jidType);
+	if (contactId == 0)
+		return false;
+
+	QDateTime dTime;
+	int nType = 0;
+
+	if (e->type() == PsiEvent::Message) {
+		MessageEvent::Ptr me = e.staticCast<MessageEvent>();
+		const Message &m = me->message();
+		dTime = m.timeStamp();
+		if (m.type() == "chat")
+			nType = 1;
+		else if(m.type() == "error")
+			nType = 4;
+		else if(m.type() == "headline")
+			nType = 5;
+
+	} else if (e->type() == PsiEvent::Auth) {
+		AuthEvent::Ptr ae = e.staticCast<AuthEvent>();
+		dTime = ae->timeStamp();
+		QString subType = ae->authType();
+		if(subType == "subscribe")
+			nType = 3;
+		else if(subType == "subscribed")
+			nType = 6;
+		else if(subType == "unsubscribe")
+			nType = 7;
+		else if(subType == "unsubscribed")
+			nType = 8;
+	} else
+		return false;
+
+	int nDirection = e->originLocal() ? 1 : 2;
+	if (!transaction(false))
+		return false;
+
+	PreparedQuery *query = queryes.getPreparedQuery(QueryInsertEvent, false, false);
+	query->bindValue(":contact_id", contactId);
+	query->bindValue(":resource", (jidType != GroupChatContact) ? jid.resource() : "");
+	query->bindValue(":date", dTime);
+	query->bindValue(":type", nType);
+	query->bindValue(":direction", nDirection);
+	if (nType == 0 || nType == 1 || nType == 4 || nType == 5) {
+		MessageEvent::Ptr me = e.staticCast<MessageEvent>();
+		const Message &m = me->message();
+		QString lang = m.lang();
+		query->bindValue(":subject", m.subject(lang));
+		query->bindValue(":m_text", m.body(lang));
+		query->bindValue(":lang", lang);
+		QString extraData;
+		const UrlList &urls = m.urlList();
+		if (!urls.isEmpty()) {
+			QVariantMap xepList;
+			QVariantList urlList;
+			foreach (const Url &url, urls)
+				if (!url.url().isEmpty()) {
+					QVariantList urlItem;
+					urlItem.append(QVariant(url.url()));
+					if (!url.desc().isEmpty())
+						urlItem.append(QVariant(url.desc()));
+					urlList.append(QVariant(urlItem));
+				}
+			xepList["jabber:x:oob"] = QVariant(urlList);
+#ifndef HAVE_QT5
+			QJson::Serializer serializer;
+			extraData = QString::fromUtf8(serializer.serialize(xepList));
+#else
+			QJsonDocument doc(QJsonObject::fromVariantMap(xepList));
+			extraData = QString::fromUtf8(doc.toBinaryData());
+#endif
+		}
+		query->bindValue(":extra_data", extraData);
+	}
+	else {
+		query->bindValue(":subject", QVariant(QVariant::String));
+		query->bindValue(":m_text", QVariant(QVariant::String));
+		query->bindValue(":lang", QVariant(QVariant::String));
+		query->bindValue(":extra_data", QVariant(QVariant::String));
+	}
+	bool res = query->exec();
+	return res;
+}
+
+PsiEvent::Ptr EDBSqLite::getEvent(const QSqlRecord &record)
+{
+	PsiAccount *pa = psi()->contactList()->getAccount(record.value("acc_id").toString());
+
+	int type = record.value("type").toInt();
+
+	if(type == 0 || type == 1 || type == 4 || type == 5) {
+		Message m;
+		m.setTimeStamp(record.value("date").toDateTime());
+		if(type == 1)
+			m.setType("chat");
+		else if(type == 4)
+			m.setType("error");
+		else if(type == 5)
+			m.setType("headline");
+		else
+			m.setType("");
+		m.setFrom(Jid(record.value("jid").toString()));
+		QVariant text = record.value("m_text");
+		if (!text.isNull()) {
+			m.setBody(text.toString());
+			m.setLang(record.value("lang").toString());
+			m.setSubject(record.value("subject").toString());
+		}
+		m.setSpooled(true);
+		QString extraStr = record.value("extra_data").toString();
+		if (!extraStr.isEmpty()) {
+#ifndef HAVE_QT5
+			QJson::Parser parser;
+#endif
+			bool fOk;
+#ifndef HAVE_QT5
+			QVariantMap extraData = parser.parse(extraStr.toUtf8(), &fOk).toMap();
+#else
+			QJsonDocument doc = QJsonDocument::fromJson(extraStr.toUtf8());
+			fOk = !doc.isNull();
+			QVariantMap extraData = doc.object().toVariantMap();
+#endif
+			if (fOk) {
+				foreach (const QVariant &urlItem, extraData["jabber:x:oob"].toList()) {
+					QVariantList itemList = urlItem.toList();
+					if (!itemList.isEmpty()) {
+						QString url = itemList.at(0).toString();
+						QString desc;
+						if (itemList.size() > 1)
+							desc = itemList.at(1).toString();
+						m.urlAdd(Url(url, desc));
+					}
+				}
+			}
+		}
+		MessageEvent::Ptr me(new MessageEvent(m, pa));
+		me->setOriginLocal((record.value("direction").toInt() == 1));
+		return me.staticCast<PsiEvent>();
+	}
+
+	if(type == 2 || type == 3 || type == 6 || type == 7 || type == 8) {
+		QString subType = "subscribe";
+		// if(type == 2) { // Not used (stupid "system message" from Psi <= 0.8.6)
+		if(type == 3)
+			subType = "subscribe";
+		else if(type == 6)
+			subType = "subscribed";
+		else if(type == 7)
+			subType = "unsubscribe";
+		else if(type == 8)
+			subType = "unsubscribed";
+
+		AuthEvent::Ptr ae(new AuthEvent(Jid(record.value("jid").toString()), subType, pa));
+		ae->setTimeStamp(record.value("date").toDateTime());
+		return ae.staticCast<PsiEvent>();
+	}
+	return PsiEvent::Ptr();
+}
+
+qint64 EDBSqLite::ensureJidRowId(const QString &accId, const XMPP::Jid &jid, int type)
+{
+	if (jid.isEmpty())
+		return 0;
+	QString sJid = (type == GroupChatContact) ? jid.full() : jid.bare();
+	QString sKey = accId + "|" + sJid;
+	qint64 id = jidsCache.value(sKey, 0);
+	if (id != 0)
+		return id;
+
+	EDBSqLite::PreparedQuery *query = queryes.getPreparedQuery(QueryJidRowId, false, false);
+	query->bindValue(":jid", sJid);
+	query->bindValue(":acc_id", accId);
+	if (query->exec()) {
+		if (query->first()) {
+			id = query->record().value("id").toLongLong();
+		} else {
+			//
+			QSqlQuery queryIns(QSqlDatabase::database("history"));
+			queryIns.prepare("INSERT INTO `contacts` (`acc_id`, `type`, `jid`, `lifetime`)"
+				" VALUES (:acc_id, :type, :jid, -1);");
+			queryIns.bindValue(":acc_id", accId);
+			queryIns.bindValue(":type", type);
+			queryIns.bindValue(":jid", sJid);
+			if (queryIns.exec()) {
+				id  = queryIns.lastInsertId().toLongLong();
+			}
+		}
+		query->freeResult();
+		if (id != 0)
+			jidsCache[sKey] = id;
+	}
+	return id;
+}
+
+int EDBSqLite::rowCount(const QString &accId, const XMPP::Jid &jid, QDateTime before)
+{
+	bool fAccAll  = accId.isEmpty();
+	bool fContAll = jid.isEmpty();
+	QueryType type;
+	if (before.isNull())
+		type = QueryRowCount;
+	else
+		type = QueryRowCountBefore;
+	PreparedQuery *query = queryes.getPreparedQuery(type, fAccAll, fContAll);
+	if (!fContAll)
+		query->bindValue(":jid", jid.full());
+	if (!fAccAll)
+		query->bindValue(":acc_id", accId);
+	if (!before.isNull())
+		query->bindValue(":date", before);
+	int res = 0;
+	if (query->exec()) {
+		if (query->next()) {
+			res = query->record().value("count").toInt();
+		}
+		query->freeResult();
+	}
+	return res;
+}
+
+bool EDBSqLite::eraseHistory(const QString &accId, const XMPP::Jid &jid)
+{
+	bool res = false;
+	if (!transaction(true))
+		return false;
+
+	if (accId.isEmpty() && jid.isEmpty()) {
+		QSqlQuery query(QSqlDatabase::database("history"));
+		//if (query.exec("DELETE FROM `events`;"))
+			if (query.exec("DELETE FROM `contacts`;")) {
+				jidsCache.clear();
+				res = true;
+			}
+	}
+	else {
+		PreparedQuery *query = queryes.getPreparedQuery(QueryJidRowId, false, false);
+		query->bindValue(":jid", jid.full());
+		query->bindValue(":acc_id", accId);
+		if (query->exec()) {
+			if (query->next()) {
+				const qint64 id = query->record().value("id").toLongLong();
+				QSqlQuery query2(QSqlDatabase::database("history"));
+				query2.prepare("DELETE FROM `events` WHERE `contact_id` = :id;");
+				query2.bindValue(":id", id);
+				if (query2.exec()) {
+					res = true;
+					query2.prepare("DELETE FROM `contacts` WHERE `id` = :id AND `lifetime` = -1;");
+					query2.bindValue(":id", id);
+					if (query2.exec()) {
+						if (query2.numRowsAffected() > 0)
+							jidsCache.clear();
+					} else
+						res = false;
+				}
+			}
+			query->freeResult();
+		}
+	}
+	if (res)
+		res = commit();
+	else
+		rollback();
+	return res;
+}
+
+bool EDBSqLite::transaction(bool now)
+{
+	if (status == NotActive)
+		return false;
+	if (now || transactionsCounter >= maxUncommitedRecs
+			|| lastCommitTime.secsTo(QDateTime::currentDateTime()) >= maxUncommitedSecs)
+		if (!commit())
+			return false;
+
+	if (status == Commited) {
+		if (!QSqlDatabase::database("history").transaction())
+			return false;
+		status = NotCommited;
+	}
+	++transactionsCounter;
+
+	startAutocommitTimer();
+
+	return true;
+}
+
+bool EDBSqLite::commit()
+{
+	if (status != NotActive) {
+		if (status == Commited || QSqlDatabase::database("history").commit()) {
+			transactionsCounter = 0;
+			lastCommitTime = QDateTime::currentDateTime();
+			status = Commited;
+			stopAutocommitTimer();
+			return true;
+		}
+	}
+	return false;
+}
+
+bool EDBSqLite::rollback()
+{
+	if (status == NotCommited && QSqlDatabase::database("history").rollback()) {
+		transactionsCounter = 0;
+		lastCommitTime = QDateTime::currentDateTime();
+		status = Commited;
+		stopAutocommitTimer();
+		return true;
+	}
+	return false;
+}
+
+void EDBSqLite::startAutocommitTimer()
+{
+	if (!commitTimer) {
+		commitTimer = new QTimer(this);
+		connect(commitTimer, SIGNAL(timeout()), this, SLOT(commit()));
+		commitTimer->setSingleShot(true);
+		commitTimer->setInterval(commitByTimeoutSecs * 1000);
+	}
+	commitTimer->start();
+}
+
+void EDBSqLite::stopAutocommitTimer()
+{
+	if (commitTimer && commitTimer->isActive())
+		commitTimer->stop();
+}
+
+bool EDBSqLite::importExecute()
+{
+	bool res = true;
+	HistoryImport *imp = new HistoryImport(psi());
+	if (imp->isNeeded()) {
+		if (imp->exec() != HistoryImport::ResultNormal) {
+			res = false;
+		}
+	}
+	delete imp;
+	return res;
+}
+
+// ****************** class PreparedQueryes ********************
+
+EDBSqLite::QueryStorage::QueryStorage()
+{
+}
+
+EDBSqLite::QueryStorage::~QueryStorage()
+{
+	foreach (EDBSqLite::PreparedQuery *q, queryList.values()) {
+		if (q)
+			delete q;
+	}
+}
+
+EDBSqLite::PreparedQuery *EDBSqLite::QueryStorage::getPreparedQuery(QueryType type, bool allAccounts, bool allContacts)
+{
+	QueryProperty queryProp(type, allAccounts, allContacts);
+	EDBSqLite::PreparedQuery *q = queryList.value(queryProp, NULL);
+	if (q != NULL)
+		return q;
+
+	q = new EDBSqLite::PreparedQuery(QSqlDatabase::database("history"));
+	q->setForwardOnly(true);
+	q->prepare(getQueryString(type, allAccounts, allContacts));
+	queryList[queryProp] = q;
+	return q;
+}
+
+EDBSqLite::PreparedQuery::PreparedQuery(QSqlDatabase db) : QSqlQuery(db)
+{
+}
+
+QString EDBSqLite::QueryStorage::getQueryString(QueryType type, bool allAccounts, bool allContacts)
+{
+	QString queryStr;
+	switch (type)
+	{
+		case QueryContactsList:
+			queryStr = "SELECT `acc_id`, `jid` FROM `contacts` WHERE `type` = :type";
+			if (!allAccounts)
+				queryStr.append(" AND `acc_id` = :acc_id");
+			queryStr.append(" ORDER BY `jid`;");
+			break;
+		case QueryLatest:
+		case QueryOldest:
+		case QueryDateBackward:
+		case QueryDateForward:
+			queryStr = "SELECT `acc_id`, `events`.`id`, `jid`, `date`, `events`.`type`, `direction`, `subject`, `m_text`, `lang`, `extra_data`"
+				" FROM `events`, `contacts`"
+				" WHERE `contacts`.`id` = `contact_id`";
+			if (!allContacts)
+				queryStr.append(" AND `jid` = :jid");
+			if (!allAccounts)
+				queryStr.append(" AND `acc_id` = :acc_id");
+			if (type == QueryDateBackward)
+				queryStr.append(" AND `date` < :date");
+			else if (type == QueryDateForward)
+				queryStr.append(" AND `date` >= :date");
+			if (type == QueryLatest || type == QueryDateBackward)
+				queryStr.append(" ORDER BY `date` DESC");
+			else
+				queryStr.append(" ORDER BY `date` ASC");
+			queryStr.append(" LIMIT :start, :cnt;");
+			break;
+		case QueryRowCount:
+		case QueryRowCountBefore:
+			queryStr = "SELECT count(*) AS `count`"
+				" FROM `events`, `contacts`"
+				" WHERE `contacts`.`id` = `contact_id`";
+			if (!allContacts)
+				queryStr.append(" AND `jid` = :jid");
+			if (!allAccounts)
+				queryStr.append(" AND `acc_id` = :acc_id");
+			if (type == QueryRowCountBefore)
+				queryStr.append(" AND `date` < :date");
+			queryStr.append(";");
+			break;
+		case QueryJidRowId:
+			queryStr = "SELECT `id` FROM `contacts` WHERE `jid` = :jid AND acc_id = :acc_id;";
+			break;
+		case QueryFindText:
+			queryStr = "SELECT `acc_id`, `events`.`id`, `jid`, `date`, `events`.`type`, `direction`, `subject`, `m_text`, `lang`, `extra_data`"
+				" FROM `events`, `contacts`"
+				" WHERE `contacts`.`id` = `contact_id`";
+			if (!allContacts)
+				queryStr.append(" AND `jid` = :jid");
+			if (!allAccounts)
+				queryStr.append(" AND `acc_id` = :acc_id");
+			queryStr.append(" AND `m_text` IS NOT NULL");
+			queryStr.append(" ORDER BY `date`;");
+			break;
+		case QueryInsertEvent:
+			queryStr = "INSERT INTO `events` ("
+				"`contact_id`, `resource`, `date`, `type`, `direction`, `subject`, `m_text`, `lang`, `extra_data`"
+				") VALUES ("
+				":contact_id, :resource, :date, :type, :direction, :subject, :m_text, :lang, :extra_data"
+				");";
+			break;
+	}
+	return queryStr;
+}
+
+uint qHash(const QueryProperty &struc)
+{
+	uint res = struc.type;
+	res <<= 8;
+	res |=  struc.allAccounts;
+	res <<= 8;
+	res |=  struc.allContacts;
+	return res;
+}
--- git.orig/src/edbsqlite.h
+++ git/src/edbsqlite.h
@@ -0,0 +1,165 @@
+/*
+ * edbsqlite.h
+ * Copyright (C) 2011   Aleksey Andreev
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef EDBSQLITE_H
+#define EDBSQLITE_H
+
+#include <QObject>
+#include <QDateTime>
+#include <QTimer>
+#include <QSqlDatabase>
+#include <QSqlQuery>
+#include <QSqlRecord>
+#include <QHash>
+#include <QVariant>
+
+#include "eventdb.h"
+#include "xmpp_jid.h"
+#include "psievent.h"
+#include "edbflatfile.h"
+
+enum QueryType {
+	QueryContactsList,
+	QueryLatest, QueryOldest,
+	QueryDateForward, QueryDateBackward,
+	QueryFindText,
+	QueryRowCount, QueryRowCountBefore,
+	QueryJidRowId,
+	QueryInsertEvent
+};
+
+struct QueryProperty
+{
+	QueryType type;
+	bool      allAccounts;
+	bool      allContacts;
+	QueryProperty(QueryType tp, bool allAcc, bool allCont) {
+		type        = tp;
+		allAccounts = allAcc;
+		allContacts = allCont;
+	}
+	bool operator==(const QueryProperty &other) const {
+		return (type == other.type && allAccounts == other.allAccounts && allContacts == other.allContacts);
+	}
+};
+uint qHash(const QueryProperty &struc);
+
+class EDBSqLite : public EDB
+{
+	Q_OBJECT
+
+	class QueryStorage;
+	class PreparedQuery : private QSqlQuery
+	{
+	public:
+		void bindValue(const QString &placeholder, const QVariant &val) { QSqlQuery::bindValue(placeholder, val); }
+		bool exec() { return QSqlQuery::exec(); }
+		bool first() { return QSqlQuery::first(); }
+		bool next() { return QSqlQuery::next(); }
+		QSqlRecord record() const { return QSqlQuery::record(); }
+		void freeResult() { QSqlQuery::finish(); }
+	private:
+		friend class QueryStorage;
+		PreparedQuery(QSqlDatabase db);
+		~PreparedQuery() {}
+	};
+	//--------
+	class QueryStorage
+	{
+	public:
+		QueryStorage();
+		~QueryStorage();
+		PreparedQuery *getPreparedQuery(QueryType type, bool allAccounts, bool allContacts);
+	private:
+		QString getQueryString(QueryType type, bool allAccounts, bool allContacts);
+	private:
+		QHash<QueryProperty, PreparedQuery *> queryList;
+	};
+	//--------
+
+public:
+	enum InsertMode { Normal, Import };
+
+	EDBSqLite(PsiCon *psi);
+	~EDBSqLite();
+	bool init();
+
+	int features() const;
+	int get(const QString &accId, const XMPP::Jid &jid, const QDateTime date, int direction, int start, int len);
+	int find(const QString &accId, const QString &str, const XMPP::Jid &jid, const QDateTime date, int direction);
+	int append(const QString &accId, const XMPP::Jid &jid, const PsiEvent::Ptr &e, int type);
+	int erase(const QString &accId, const XMPP::Jid &jid);
+	QList<ContactItem> contacts(const QString &accId, int type);
+	quint64 eventsCount(const QString &accId, const XMPP::Jid &jid);
+	QString getStorageParam(const QString &key);
+	void setStorageParam(const QString &key, const QString &val);
+
+	void setInsertingMode(InsertMode mode);
+	void setMirror(EDBFlatFile *mirr);
+	EDBFlatFile *mirror() const;
+
+private:
+	enum { NotActive, NotCommited, Commited };
+	struct item_query_req
+	{
+		QString accId;
+		XMPP::Jid j;
+		int jidType;
+		int type; // 0 = latest, 1 = oldest, 2 = random, 3 = write
+		int start;
+		int len;
+		int dir;
+		int id;
+		QDateTime date;
+		QString findStr;
+		PsiEvent::Ptr event;
+
+		enum Type { Type_get, Type_append, Type_find, Type_erase };
+	};
+	int  status;
+	unsigned int transactionsCounter;
+	QDateTime lastCommitTime;
+	unsigned int maxUncommitedRecs;
+	int maxUncommitedSecs;
+	unsigned int commitByTimeoutSecs;
+	QTimer *commitTimer;
+	EDBFlatFile *mirror_;
+	QList<item_query_req*> rlist;
+	QHash<QString, qint64>jidsCache;
+	QueryStorage queryes;
+
+private:
+	bool appendEvent(const QString &accId, const XMPP::Jid &, const PsiEvent::Ptr &, int);
+	PsiEvent::Ptr getEvent(const QSqlRecord &record);
+	qint64 ensureJidRowId(const QString &accId, const XMPP::Jid &jid, int type);
+	int  rowCount(const QString &accId, const XMPP::Jid &jid, const QDateTime before);
+	bool eraseHistory(const QString &accId, const XMPP::Jid &);
+	bool transaction(bool now);
+	bool rollback();
+	void startAutocommitTimer();
+	void stopAutocommitTimer();
+	bool importExecute();
+
+private slots:
+	void performRequests();
+	bool commit();
+};
+
+#endif // EDBSQLITE_H
--- git.orig/src/eventdb.cpp
+++ git/src/eventdb.cpp
@@ -35,12 +35,10 @@ using namespace XMPP;
 //----------------------------------------------------------------------------
 // EDBItem
 //----------------------------------------------------------------------------
-EDBItem::EDBItem(const PsiEvent::Ptr &event, const QString &id, const QString &prevId, const QString &nextId)
+EDBItem::EDBItem(const PsiEvent::Ptr &event, const QString &id)
 {
 	e = event;
 	v_id = id;
-	v_prevId = prevId;
-	v_nextId = nextId;
 }
 
 EDBItem::~EDBItem()
@@ -57,16 +55,6 @@ const QString & EDBItem::id() const
 	return v_id;
 }
 
-const QString & EDBItem::nextId() const
-{
-	return v_nextId;
-}
-
-const QString & EDBItem::prevId() const
-{
-	return v_prevId;
-}
-
 
 //----------------------------------------------------------------------------
 // EDBHandle
@@ -77,6 +65,7 @@ public:
 	Private() {}
 
 	EDB *edb;
+	int beginRow_;
 	EDBResult r;
 	bool busy;
 	bool writeSuccess;
@@ -89,6 +78,7 @@ EDBHandle::EDBHandle(EDB *edb)
 {
 	d = new Private;
 	d->edb = edb;
+	d->beginRow_ = 0;
 	d->busy = false;
 	d->writeSuccess = false;
 	d->listeningFor = -1;
@@ -104,53 +94,32 @@ EDBHandle::~EDBHandle()
 	delete d;
 }
 
-void EDBHandle::getLatest(const Jid &j, int len)
+void EDBHandle::get(const QString &accId, const XMPP::Jid &jid, const QDateTime date, int direction, int begin, int len)
 {
 	d->busy = true;
 	d->lastRequestType = Read;
-	d->listeningFor = d->edb->op_getLatest(j, len);
+	d->listeningFor = d->edb->op_get(accId, jid, date, direction, begin, len);
 }
 
-void EDBHandle::getOldest(const Jid &j, int len)
+void EDBHandle::find(const QString &accId, const QString &str, const XMPP::Jid &jid, const QDateTime date, int direction)
 {
 	d->busy = true;
 	d->lastRequestType = Read;
-	d->listeningFor = d->edb->op_getOldest(j, len);
+	d->listeningFor = d->edb->op_find(accId, str, jid, date, direction);
 }
 
-void EDBHandle::get(const Jid &j, const QString &id, int direction, int len)
-{
-	d->busy = true;
-	d->lastRequestType = Read;
-	d->listeningFor = d->edb->op_get(j, id, direction, len);
-}
-
-void EDBHandle::getByDate(const Jid &j, QDateTime first, QDateTime last)
-{
-	d->busy = true;
-	d->lastRequestType = Read;
-	d->listeningFor = d->edb->op_getByDate(j, first, last);
-}
-
-void EDBHandle::find(const QString &str, const Jid &j, const QString &id, int direction)
-{
-	d->busy = true;
-	d->lastRequestType = Read;
-	d->listeningFor = d->edb->op_find(str, j, id, direction);
-}
-
-void EDBHandle::append(const Jid &j, const PsiEvent::Ptr &e)
+void EDBHandle::append(const QString &accId, const Jid &j, const PsiEvent::Ptr &e, int type)
 {
 	d->busy = true;
 	d->lastRequestType = Write;
-	d->listeningFor = d->edb->op_append(j, e);
+	d->listeningFor = d->edb->op_append(accId, j, e, type);
 }
 
-void EDBHandle::erase(const Jid &j)
+void EDBHandle::erase(const QString &accId, const Jid &j)
 {
 	d->busy = true;
 	d->lastRequestType = Erase;
-	d->listeningFor = d->edb->op_erase(j);
+	d->listeningFor = d->edb->op_erase(accId, j);
 }
 
 bool EDBHandle::busy() const
@@ -194,6 +163,11 @@ int EDBHandle::lastRequestType() const
 	return d->lastRequestType;
 }
 
+int EDBHandle::beginRow() const
+{
+	return d->beginRow_;
+}
+
 
 //----------------------------------------------------------------------------
 // EDB
@@ -237,46 +211,32 @@ void EDB::unreg(EDBHandle *h)
 	d->list.removeAll(h);
 }
 
-int EDB::op_getLatest(const Jid &j, int len)
-{
-	return getLatest(j, len);
-}
-
-int EDB::op_getOldest(const Jid &j, int len)
-{
-	return getOldest(j, len);
-}
-
-int EDB::op_get(const Jid &jid, const QString &id, int direction, int len)
-{
-	return get(jid, id, direction, len);
-}
-
-int EDB::op_getByDate(const Jid &j, QDateTime first, QDateTime last)
+int EDB::op_get(const QString &accId, const Jid &jid, const QDateTime date, int direction, int start, int len)
 {
-	return getByDate(j, first, last);
+	return get(accId, jid, date, direction, start, len);
 }
 
-int EDB::op_find(const QString &str, const Jid &j, const QString &id, int direction)
+int EDB::op_find(const QString &accId, const QString &str, const Jid &j, const QDateTime date, int direction)
 {
-	return find(str, j, id, direction);
+	return find(accId, str, j, date, direction);
 }
 
-int EDB::op_append(const Jid &j, const PsiEvent::Ptr &e)
+int EDB::op_append(const QString &accId, const Jid &j, const PsiEvent::Ptr &e, int type)
 {
-	return append(j, e);
+	return append(accId, j, e, type);
 }
 
-int EDB::op_erase(const Jid &j)
+int EDB::op_erase(const QString &accId, const Jid &j)
 {
-	return erase(j);
+	return erase(accId, j);
 }
 
-void EDB::resultReady(int req, EDBResult r)
+void EDB::resultReady(int req, EDBResult r, int begin_row)
 {
 	// deliver
 	foreach(EDBHandle* h, d->list) {
 		if(h->listeningFor() == req) {
+			h->d->beginRow_ = begin_row;
 			h->edb_resultReady(r);
 			return;
 		}
--- git.orig/src/eventdb.h
+++ git/src/eventdb.h
@@ -34,16 +34,14 @@
 class EDBItem
 {
 public:
-	EDBItem(const PsiEvent::Ptr &, const QString &id, const QString &nextId, const QString &prevId);
+	EDBItem(const PsiEvent::Ptr &, const QString &id);
 	~EDBItem();
 
 	PsiEvent::Ptr event() const;
 	const QString & id() const;
-	const QString & nextId() const;
-	const QString & prevId() const;
 
 private:
-	QString v_id, v_prevId, v_nextId;
+	QString v_id;
 	PsiEvent::Ptr e;
 };
 
@@ -60,18 +58,16 @@ public:
 	~EDBHandle();
 
 	// operations
-	void getLatest(const XMPP::Jid &, int len);
-	void getOldest(const XMPP::Jid &, int len);
-	void get(const XMPP::Jid &jid, const QString &id, int direction, int len);
-	void getByDate(const XMPP::Jid &jid, QDateTime first, QDateTime last);
-	void find(const QString &, const XMPP::Jid &, const QString &id, int direction);
-	void append(const XMPP::Jid &, const PsiEvent::Ptr &);
-	void erase(const XMPP::Jid &);
+	void get(const QString &accId, const XMPP::Jid &jid, const QDateTime date, int direction, int begin, int len);
+	void find(const QString &accId, const QString &, const XMPP::Jid &, const QDateTime date, int direction);
+	void append(const QString &accId, const XMPP::Jid &, const PsiEvent::Ptr &, int);
+	void erase(const QString &accId, const XMPP::Jid &);
 
 	bool busy() const;
 	const EDBResult result() const;
 	bool writeSuccess() const;
 	int lastRequestType() const;
+	int beginRow() const;
 
 signals:
 	void finished();
@@ -104,17 +100,17 @@ public:
 	virtual ~EDB()=0;
 	virtual int features() const = 0;
 	virtual QList<ContactItem> contacts(const QString &accId, int type) = 0;
+	virtual quint64 eventsCount(const QString &accId, const XMPP::Jid &jid) = 0;
+	virtual QString getStorageParam(const QString &key) = 0;
+	virtual void setStorageParam(const QString &key, const QString &val) = 0;
 
 protected:
 	int genUniqueId() const;
-	virtual int getLatest(const XMPP::Jid &, int len)=0;
-	virtual int getOldest(const XMPP::Jid &, int len)=0;
-	virtual int get(const XMPP::Jid &jid, const QString &id, int direction, int len)=0;
-	virtual int getByDate(const XMPP::Jid &jid, QDateTime first, QDateTime last) = 0;
-	virtual int append(const XMPP::Jid &, const PsiEvent::Ptr &)=0;
-	virtual int find(const QString &, const XMPP::Jid &, const QString &id, int direction)=0;
-	virtual int erase(const XMPP::Jid &)=0;
-	void resultReady(int, EDBResult);
+	virtual int get(const QString &accId, const XMPP::Jid &jid, const QDateTime date, int direction, int start, int len)=0;
+	virtual int append(const QString &accId, const XMPP::Jid &, const PsiEvent::Ptr &, int)=0;
+	virtual int find(const QString &accId, const QString &, const XMPP::Jid &, const QDateTime date, int direction)=0;
+	virtual int erase(const QString &accId, const XMPP::Jid &)=0;
+	void resultReady(int, EDBResult, int);
 	void writeFinished(int, bool);
 	PsiCon *psi();
 
@@ -126,13 +122,10 @@ private:
 	void reg(EDBHandle *);
 	void unreg(EDBHandle *);
 
-	int op_getLatest(const XMPP::Jid &, int len);
-	int op_getOldest(const XMPP::Jid &, int len);
-	int op_get(const XMPP::Jid &, const QString &id, int direction, int len);
-	int op_getByDate(const XMPP::Jid &jid, QDateTime first, QDateTime last);
-	int op_find(const QString &, const XMPP::Jid &, const QString &id, int direction);
-	int op_append(const XMPP::Jid &, const PsiEvent::Ptr&);
-	int op_erase(const XMPP::Jid &);
+	int op_get(const QString &accId, const XMPP::Jid &, const QDateTime date, int direction, int start, int len);
+	int op_find(const QString &accId, const QString &, const XMPP::Jid &, const QDateTime date, int direction);
+	int op_append(const QString &accId, const XMPP::Jid &, const PsiEvent::Ptr &, int);
+	int op_erase(const QString &accId, const XMPP::Jid &);
 };
 
 #endif
--- git.orig/src/historydlg.cpp
+++ git/src/historydlg.cpp
@@ -38,6 +38,9 @@
 #include "userlist.h"
 #include "common.h"
 
+#define SEARCH_PADDING_SIZE 20
+#define DISPLAY_PAGE_SIZE   200
+
 static const QString geometryOption = "options.ui.history.size";
 
 static QString getNext(QString *str)
@@ -96,23 +99,519 @@ static QStringList wrapString(const QStr
 }
 
 
+SearchProxy::SearchProxy(PsiCon *p, DisplayProxy *d)
+	: QObject(0)
+	, active(false)
+{
+	psi = p;
+	dp = d;
+}
+
+void SearchProxy::find(const QString &str, const QString &acc_id, const Jid &jid, int dir)
+{
+	if (!active || str != s_string || acc_id != acc_ || jid != jid_)
+	{
+		active = true;
+		s_string = str;
+		acc_ = acc_id;
+		jid_ = jid;
+		direction = dir;
+		emit needRequest();
+		reqType = ReqFind;
+		getEDBHandle()->find(acc_id, str, jid, QDateTime(), EDB::Forward);
+		return;
+	}
+
+	movePosition(dir);
+	if (!dp->moveSearchCursor(dir, 1)) { // tries to move the search cursor into the history widget
+		direction = dir;
+		emit needRequest();
+		reqType = ReqPadding;
+		int r_dir = (dir == EDB::Forward) ? EDB::Backward : EDB::Forward;
+		getEDBHandle()->get(acc_id, jid, position.date, r_dir, 0, SEARCH_PADDING_SIZE);
+	}
+}
+
+void SearchProxy::handleResult()
+{
+	EDBHandle *h = qobject_cast<EDBHandle*>(sender());
+	if (!h)
+		return;
+
+	const EDBResult r = h->result();
+	switch (reqType) {
+	case ReqFind:
+		handleFoundData(r);
+		emit found(total_found);
+		break;
+	case ReqPadding:
+		handlePadding(r);
+		break;
+	}
+	delete h;
+}
+
+void SearchProxy::reset()
+{
+	active = false;
+	acc_ = "";
+	jid_ = XMPP::Jid();
+	s_string = "";
+	map.clear();
+	list.clear();
+	total_found = 0;
+	general_pos = 0;
+}
+
+EDBHandle *SearchProxy::getEDBHandle()
+{
+	EDBHandle *h = new EDBHandle(psi->edb());
+	connect(h, SIGNAL(finished()), this, SLOT(handleResult()));
+	return h;
+}
+
+void SearchProxy::movePosition(int dir)
+{
+	int idx = map.value(position.date.toTime_t(), -1);
+	Q_ASSERT(idx >= 0 && idx < list.count());
+	if (dir == EDB::Forward)
+	{
+		if (list.at(idx).num == position.num)
+		{
+			position.num = 1;
+			if (idx == list.size() - 1)
+			{
+				position.date = list.at(0).date;
+				general_pos = 0;
+			}
+			else
+				position.date = list.at(idx + 1).date;
+		}
+		else
+			++position.num;
+		++general_pos;
+	}
+	else
+	{
+		if (position.num == 1)
+		{
+			if (idx == 0)
+			{
+				general_pos = total_found +1;
+				position = list.last();
+			}
+			else
+				position = list.at(idx - 1);
+		}
+		else
+			--position.num;
+		--general_pos;
+	}
+}
+
+int SearchProxy::invertSearchPosition(const Position &pos, int dir)
+{
+	int num = pos.num;
+	if (dir == EDB::Backward)
+	{
+		int idx = map.value(pos.date.toTime_t(), -1);
+		Q_ASSERT(idx >= 0 && idx < list.count());
+		num = list.at(idx).num - pos.num + 1;
+		Q_ASSERT(num > 0);
+	}
+	return num;
+}
+
+void SearchProxy::handleFoundData(const EDBResult &r)
+{
+	int cnt = r.count();
+	if (cnt == 0)
+	{
+		reset();
+		return;
+	}
+
+	total_found = 0;
+	position.num = 1;
+	map.clear();
+	list.clear();
+	for (int i = 0; i < cnt; ++i)
+	{
+		EDBItemPtr item = r.value(i);
+		PsiEvent::Ptr e(item->event());
+		MessageEvent::Ptr me = e.staticCast<MessageEvent>();
+		int m = me->message().body().count(s_string, Qt::CaseInsensitive);
+		Position pos;
+		pos.date = me->timeStamp();
+		uint t = pos.date.toTime_t();
+		int idx = map.value(t, -1);
+		if (idx == -1)
+		{
+			pos.num = m;
+			map.insert(t, list.size());
+			list.append(pos);
+		}
+		else
+			list[idx].num += m;
+		if ((direction == EDB::Forward) ? (i == 0) : (i == cnt - 1))
+			position.date = pos.date;
+		total_found += m;
+	}
+	position.num = invertSearchPosition(position, direction);
+	general_pos = (direction == EDB::Forward) ? 1 : total_found;
+
+	reqType = ReqPadding;
+	int r_dir = (direction == EDB::Forward) ? EDB::Backward : EDB::Forward;
+	getEDBHandle()->get(acc_, jid_, position.date, r_dir, 0, SEARCH_PADDING_SIZE);
+}
+
+void SearchProxy::handlePadding(const EDBResult &r)
+{
+	int s_pos = invertSearchPosition(position, direction);
+
+	int cnt = r.count();
+	if (cnt == 0)
+	{
+		dp->displayWithSearchCursor(acc_, jid_, position.date, direction, s_string, s_pos);
+		return;
+	}
+
+	EDBItemPtr item = r.value(cnt - 1);
+	PsiEvent::Ptr e(item->event());
+	QDateTime s_date = e->timeStamp();
+	int idx = map.value(position.date.toTime_t());
+	int off = ( direction == EDB::Forward) ? -1 : 1;
+	idx += off;
+	while (idx >= 0 && idx < list.count())
+	{
+		const Position &pos = list.at(idx);
+		if ((direction == EDB::Forward) ? (pos.date < s_date) : (pos.date > s_date))
+			break;
+		s_pos += pos.num;
+		idx += off;
+	}
+	dp->displayWithSearchCursor(acc_, jid_, s_date, direction, s_string, s_pos);
+}
+
+
+DisplayProxy::DisplayProxy(PsiCon *p, PsiTextView *v)
+	: QObject(0)
+{
+	psi = p;
+	viewWid = v;
+	reqType = ReqNone;
+	can_backward = false;
+	can_forward = false;
+	searchParams.searchPos = 0;
+	searchParams.cursorPos = -1;
+}
+
+void DisplayProxy::displayEarliest(const QString &acc_id, const Jid &jid)
+{
+	acc_ = acc_id;
+	jid_ = jid;
+	resetSearch();
+	updateQueryParams(EDB::Forward, 0);
+	reqType = ReqEarliest;
+	getEDBHandle()->get(acc_id, jid, QDateTime(), EDB::Forward, 0, DISPLAY_PAGE_SIZE);
+}
+
+void DisplayProxy::displayLatest(const QString &acc_id, const Jid &jid)
+{
+	acc_ = acc_id;
+	jid_ = jid;
+	resetSearch();
+	updateQueryParams(EDB::Backward, 0);
+	reqType = ReqLatest;
+	getEDBHandle()->get(acc_id, jid, QDateTime(), EDB::Backward, 0, DISPLAY_PAGE_SIZE);
+}
+
+void DisplayProxy::displayFromDate(const QString &acc_id, const Jid &jid, const QDateTime date)
+{
+	acc_ = acc_id;
+	jid_ = jid;
+	resetSearch();
+	updateQueryParams(EDB::Forward, 0, date);
+	reqType = ReqDate;
+	getEDBHandle()->get(acc_id, jid, date, EDB::Forward, 0, DISPLAY_PAGE_SIZE);
+}
+
+void DisplayProxy::displayNext()
+{
+	resetSearch();
+	updateQueryParams(EDB::Forward, DISPLAY_PAGE_SIZE);
+	reqType = ReqNext;
+	getEDBHandle()->get(acc_, jid_, queryParams.date, queryParams.direction, queryParams.offset, DISPLAY_PAGE_SIZE);
+}
+
+void DisplayProxy::displayPrevious()
+{
+	resetSearch();
+	updateQueryParams(EDB::Backward, DISPLAY_PAGE_SIZE);
+	reqType = ReqPrevious;
+	getEDBHandle()->get(acc_, jid_, queryParams.date, queryParams.direction, queryParams.offset, DISPLAY_PAGE_SIZE);
+}
+
+bool DisplayProxy::moveSearchCursor(int dir, int n)
+{
+	if (n == 0 || searchParams.searchPos == 0 || searchParams.searchString.isEmpty())
+		return false;
+
+	// setting the start cursor position
+	QTextCursor t_cursor = viewWid->textCursor();
+	bool shiftCursor = false;
+	if (searchParams.cursorPos == -1)
+	{
+		if (dir == EDB::Forward)
+			t_cursor.movePosition(QTextCursor::Start);
+		else
+			t_cursor.movePosition(QTextCursor::End);
+	}
+	else
+	{
+		t_cursor.setPosition(searchParams.cursorPos);
+		if (dir == EDB::Forward)
+			shiftCursor = true;
+	}
+	viewWid->setTextCursor(t_cursor);
+
+	// settings of the text search options
+	QTextDocument::FindFlags find_opt;
+	if (dir == EDB::Backward)
+		find_opt |= QTextDocument::FindBackward;
+
+	// moving the text cursor
+	bool res = true;
+	for (; n != 0; --n)
+	{
+		if (shiftCursor)
+		{
+			t_cursor = viewWid->textCursor();
+			t_cursor.movePosition(QTextCursor::Right);
+			viewWid->setTextCursor(t_cursor);
+		}
+		else if (dir == EDB::Forward)
+			shiftCursor = true;
+
+		if (!(res = viewWid->find(searchParams.searchString, find_opt)))
+			break;
+	}
+
+	t_cursor = viewWid->textCursor();
+	t_cursor.setPosition(t_cursor.selectionStart());
+	// save the text cursor position
+	searchParams.cursorPos = t_cursor.position();
+
+	if (res)
+		emit searchCursorMoved();
+	return res;
+}
+
+void DisplayProxy::displayWithSearchCursor(const QString &acc_id, const Jid &jid, QDateTime start, int dir, const QString &s_str, int num)
+{
+	acc_ = acc_id;
+	jid_ = jid;
+
+	searchParams.searchDir = dir;
+	searchParams.searchPos = num;
+	searchParams.cursorPos = -1;
+	searchParams.searchString = s_str;
+
+	QDateTime ts = start;
+	if (dir == EDB::Backward && !ts.isNull())
+		ts = ts.addSecs(1);
+	updateQueryParams(dir, 0, ts);
+
+	reqType = ReqDate;
+	getEDBHandle()->get(acc_id, jid, queryParams.date, queryParams.direction, 0, DISPLAY_PAGE_SIZE);
+}
+
+void DisplayProxy::handleResult()
+{
+	EDBHandle *h = qobject_cast<EDBHandle*>(sender());
+	if (!h)
+		return;
+
+	const EDBResult r = h->result();
+	can_backward = true;
+	can_forward  = true;
+	if (r.count() < DISPLAY_PAGE_SIZE)
+	{
+		switch (reqType) {
+		case ReqEarliest:
+		case ReqNext:
+			can_forward = false;
+			break;
+		case ReqLatest:
+		case ReqPrevious:
+			can_backward = false;
+			break;
+		case ReqDate:
+			if (searchParams.searchPos == 0)
+				can_forward = false;
+			else
+			{
+				if (searchParams.searchDir == EDB::Forward)
+					can_forward = false;
+				else
+					can_backward = false;
+			}
+			break;
+		default:
+			break;
+		}
+		if (r.count() == 0)
+		{
+			emit updated();
+			delete h;
+			return;
+		}
+	}
+	if (queryParams.offset == 0 && queryParams.date.isNull())
+	{
+		if (queryParams.direction == EDB::Forward)
+			can_backward = false;
+		else
+			can_forward = false;
+	}
+	switch (reqType) {
+	case ReqDate:
+		displayResult(r, queryParams.direction);
+		moveSearchCursor(searchParams.searchDir, searchParams.searchPos);
+		break;
+	case ReqEarliest:
+		can_backward = false;
+		displayResult(r, queryParams.direction);
+		break;
+	case ReqLatest:
+		can_forward = false;
+	case ReqNext:
+	case ReqPrevious:
+		displayResult(r, queryParams.direction);
+		break;
+	default:
+		break;
+	}
+	delete h;
+}
+
+EDBHandle *DisplayProxy::getEDBHandle()
+{
+	EDBHandle *h = new EDBHandle(psi->edb());
+	connect(h, SIGNAL(finished()), this, SLOT(handleResult()));
+	return h;
+}
+
+void DisplayProxy::resetSearch()
+{
+	searchParams.searchPos = 0;
+	searchParams.cursorPos = -1;
+	searchParams.searchString = "";
+}
+
+void DisplayProxy::updateQueryParams(int dir, int increase, QDateTime date)
+{
+	if (increase == 0)
+	{
+		queryParams.direction = dir;
+		queryParams.offset = 0;
+		queryParams.date = date;
+	}
+	else if (queryParams.offset != 0 || dir == queryParams.direction || !queryParams.date.isNull())
+	{
+		if (dir == queryParams.direction)
+			queryParams.offset += increase;
+		else
+		{
+			if (queryParams.offset == 0)
+				queryParams.direction = dir;
+			else
+			{
+				Q_ASSERT(queryParams.offset >= increase);
+				queryParams.offset -= increase;
+			}
+		}
+	}
+}
+
+void DisplayProxy::displayResult(const EDBResult &r, int dir)
+{
+	viewWid->clear();
+	int i, d;
+	if (dir == EDB::Forward)
+	{
+		i = 0;
+		d = 1;
+	}
+	else
+	{
+		i = r.count() - 1;
+		d = -1;
+	}
+
+	bool fAllContacts = jid_.isEmpty();
+	while (i >= 0 && i < r.count())
+	{
+		EDBItemPtr item = r.value(i);
+		PsiEvent::Ptr e(item->event());
+		if (e->type() == PsiEvent::Message) {
+			PsiAccount *pa = e->account();
+			QString from = getNick(e->account(), e->from());
+			MessageEvent::Ptr me = e.staticCast<MessageEvent>();
+			QString msg = me->message().body();
+			msg = TextUtil::linkify(TextUtil::plain2rich(msg));
+
+			if (emoticons)
+				msg = TextUtil::emoticonify(msg);
+			if (formatting)
+				msg = TextUtil::legacyFormat(msg);
+
+			if (me->originLocal())
+			{
+				QString nick = (pa) ? TextUtil::plain2rich(pa->nick()) : tr("deleted");
+				msg = "<span style='color:" + sentColor + "'>" + me->timeStamp().toString("[dd.MM.yyyy hh:mm:ss]") + " &lt;" + nick
+					+ ((fAllContacts) ? QString(" -> %1").arg(TextUtil::plain2rich(from)) : QString())
+					+ "&gt; " + msg + "</span>";
+			}
+			else
+				msg = "<span style='color:" + receivedColor + "'>" + me->timeStamp().toString("[dd.MM.yyyy hh:mm:ss]") + " &lt;"
+					+  TextUtil::plain2rich(from) + "&gt; " + msg + "</span>";
+
+			viewWid->appendText(msg);
+		}
+		i += d;
+	}
+	viewWid->verticalScrollBar()->setValue(viewWid->verticalScrollBar()->maximum());
+	emit updated();
+}
+
+QString DisplayProxy::getNick(PsiAccount *pa, const Jid &jid) const
+{
+	QString from;
+	if (pa)
+	{
+		UserListItem *u = pa->findFirstRelevant(jid);
+		if (u && !u->name().trimmed().isEmpty())
+			from = u->name().trimmed();
+	}
+	if (from.isEmpty())
+	{
+		from = jid.resource().trimmed(); // for offline conferences
+		if (from.isEmpty())
+			from = jid.node();
+	}
+	return from;
+}
+
 class HistoryDlg::Private
 {
 public:
 	Jid jid;
 	PsiAccount *pa;
 	PsiCon *psi;
-	QString id_prev, id_begin, id_end, id_next;
-	HistoryDlg::RequestType reqType;
-	QString findStr;
-	QDate date;
 #ifndef HAVE_X11
 	bool autoCopyText;
 #endif
-	bool formatting;
-	bool emoticons;
-	QString sentColor;
-	QString receivedColor;
 };
 
 HistoryDlg::HistoryDlg(const Jid &jid, PsiAccount *pa)
@@ -123,11 +622,17 @@ HistoryDlg::HistoryDlg(const Jid &jid, P
 	setAttribute(Qt::WA_DeleteOnClose);
 	setModal(false);
 	d = new Private;
-	d->reqType = TypeNone;
-	d->pa = pa;
 	d->psi = pa->psi();
 	d->jid = jid;
-	d->pa->dialogRegister(this, d->jid);
+	d->pa = pa;
+	pa->dialogRegister(this, d->jid);
+
+	displayProxy = new DisplayProxy(pa->psi(), ui_.msgLog);
+	searchProxy = new SearchProxy(pa->psi(), displayProxy);
+	connect(displayProxy, SIGNAL(updated()), this, SLOT(viewUpdated()));
+	connect(displayProxy, SIGNAL(searchCursorMoved()), this, SLOT(updateSearchHint()));
+	connect(searchProxy, SIGNAL(found(int)), this, SLOT(showFoundResult(int)));
+	connect(searchProxy, SIGNAL(needRequest()), this, SLOT(startRequest()));
 
 	//workaround calendar size
 	int minWidth = ui_.calendar->minimumSizeHint().width();
@@ -142,20 +647,23 @@ HistoryDlg::HistoryDlg(const Jid &jid, P
 #ifndef Q_OS_MAC
 	setWindowIcon(IconsetFactory::icon("psi/history").icon());
 #endif
-	ui_.tb_find->setIcon(IconsetFactory::icon("psi/search").icon());
+	ui_.tbFindForward->setIcon(IconsetFactory::icon("psi/arrowDown").icon());
+	ui_.tbFindBackward->setIcon(IconsetFactory::icon("psi/arrowUp").icon());
 
 	ui_.msgLog->setFont(fontForOption("options.ui.look.font.chat"));
+	setLooks(ui_.msgLog);
 	ui_.contactList->setFont(fontForOption("options.ui.look.font.contactlist"));
 
 	ui_.calendar->setFirstDayOfWeek(firstDayOfWeekFromLocale());
 
 	connect(ui_.searchField, SIGNAL(returnPressed()), SLOT(findMessages()));
-	connect(ui_.searchField, SIGNAL(textChanged(const QString)), SLOT(highlightBlocks(const QString)));
+	connect(ui_.searchField, SIGNAL(textChanged(const QString)), SLOT(highlightBlocks()));
 	connect(ui_.buttonPrevious, SIGNAL(released()), SLOT(getPrevious()));
 	connect(ui_.buttonNext, SIGNAL(released()), SLOT(getNext()));
 	connect(ui_.buttonRefresh, SIGNAL(released()), SLOT(refresh()));
 	connect(ui_.contactList, SIGNAL(clicked(QModelIndex)), SLOT(openSelectedContact()));
-	connect(ui_.tb_find, SIGNAL(clicked()), SLOT(findMessages()));
+	connect(ui_.tbFindForward, SIGNAL(clicked()), SLOT(findMessages()));
+	connect(ui_.tbFindBackward, SIGNAL(clicked()), SLOT(findMessages()));
 	connect(ui_.buttonLastest, SIGNAL(released()), SLOT(getLatest()));
 	connect(ui_.buttonEarliest, SIGNAL(released()), SLOT(getEarliest()));
 	connect(ui_.calendar, SIGNAL(selectionChanged()), SLOT(getDate()));
@@ -171,7 +679,7 @@ HistoryDlg::HistoryDlg(const Jid &jid, P
 	optionUpdated("options.ui.chat.legacy-formatting");
 	connect(PsiOptions::instance(), SIGNAL(optionChanged(QString)), SLOT(optionUpdated(QString)));
 
-	connect(d->pa, SIGNAL(removedContact(PsiContact*)), SLOT(removedContact(PsiContact*)));
+	connect(pa, SIGNAL(removedContact(PsiContact*)), SLOT(removedContact(PsiContact*)));
 
 	listAccounts();
 
@@ -200,6 +708,8 @@ HistoryDlg::HistoryDlg(const Jid &jid, P
 HistoryDlg::~HistoryDlg()
 {
 	delete d;
+	delete searchProxy;
+	delete displayProxy;
 }
 
 bool HistoryDlg::eventFilter(QObject *obj, QEvent *e)
@@ -274,8 +784,7 @@ QFont HistoryDlg::fontForOption(const QS
 
 void HistoryDlg::changeAccount(const QString /*accountName*/)
 {
-	ui_.msgLog->clear();
-	setButtons(false);
+	resetWidgets();
 	d->jid = QString();
 	QString paId = ui_.accountsBox->itemData(ui_.accountsBox->currentIndex()).toString();
 	contactListModel()->updateContacts(d->psi, paId);
@@ -344,15 +853,24 @@ void HistoryDlg::restoreFocus()
 		setFocus();
 }
 
+void HistoryDlg::resetWidgets()
+{
+	ui_.msgLog->clear();
+	ui_.searchField->clear();
+	ui_.searchResult->setVisible(false);
+	ui_.searchResult->clear();
+}
+
 void HistoryDlg::listAccounts()
 {
+	ui_.accountsBox->addItem(IconsetFactory::icon("psi/account").icon(), tr("All accounts"), QVariant());
 	if (d->psi)
 	{
 		foreach (PsiAccount* account, d->psi->contactList()->enabledAccounts())
 			ui_.accountsBox->addItem(IconsetFactory::icon("psi/account").icon(), account->nameWithJid(), QVariant(account->id()));
 	}
 	//select active account
-	ui_.accountsBox->setCurrentIndex(ui_.accountsBox->findData(d->pa->id()));
+	ui_.accountsBox->setCurrentIndex(ui_.accountsBox->findData(getCurrentAccountId()));
 	//connect signal after the list is populated to prevent execution in the middle of the loop
 	connect(ui_.accountsBox, SIGNAL(currentIndexChanged(const QString)), SLOT(changeAccount(const QString)));
 }
@@ -395,18 +913,16 @@ void HistoryDlg::openSelectedContact()
 	}
 }
 
-void HistoryDlg::highlightBlocks(const QString text)
+void HistoryDlg::highlightBlocks()
 {
+	const QString text = ui_.searchField->text();
 	QTextCursor cur = ui_.msgLog->textCursor();
+	QTextCursor old = cur;
+	int sc_pos = ui_.msgLog->verticalScrollBar()->value();
 	cur.clearSelection();
 	cur.movePosition(QTextCursor::Start);
 	ui_.msgLog->setTextCursor(cur);
 
-	if (text.isEmpty()) {
-		getLatest();
-		return;
-	}
-
 	QList<QTextEdit::ExtraSelection> extras;
 	QTextEdit::ExtraSelection highlight;
 	highlight.format.setBackground(Qt::yellow);
@@ -421,23 +937,27 @@ void HistoryDlg::highlightBlocks(const Q
 	}
 
 	ui_.msgLog->setExtraSelections(extras);
+	ui_.msgLog->setTextCursor(old);
+	ui_.msgLog->verticalScrollBar()->setValue(sc_pos);
 }
 
 void HistoryDlg::findMessages()
 {
-	//get the oldest event as a starting point
-	startRequest();
-	d->reqType = TypeFindOldest;
-	getEDBHandle()->getOldest(d->jid, 1);
+	const QString str = ui_.searchField->text();
+	if (str.isEmpty())
+		return;
+
+	int dir = (sender() != ui_.tbFindBackward) ? EDB::Forward : EDB::Backward;
+	searchProxy->find(str, getCurrentAccountId(), d->jid, dir);
 }
 
 void HistoryDlg::removeHistory()
 {
 	int res = QMessageBox::question(this, tr("Remove history"),
-					tr("Are you sure you want to completely remove history for a contact %1?").arg(d->jid.bare())
+					tr("Are you sure you want to completely remove history for a contact %1?").arg(d->jid.full())
 					,QMessageBox::Ok | QMessageBox::Cancel);
 	if(res == QMessageBox::Ok) {
-		getEDBHandle()->erase(d->jid);
+		getEDBHandle()->erase(getCurrentAccountId(), d->jid);
 		QModelIndex i_index = ui_.contactList->selectionModel()->currentIndex();
 		QModelIndex p_index = i_index.parent();
 		QString p_id = p_index.data(HistoryContactListModel::ItemIdRole).toString();
@@ -465,7 +983,7 @@ void HistoryDlg::exportHistory()
 		them = JIDUtil::nickOrJid(u->name(), u->jid().full());
 	else
 		them = d->jid.full();
-	QString s = JIDUtil::encode(them).toLower();
+	QString s = (!them.isEmpty()) ? JIDUtil::encode(them).toLower() : "all_contacts";
 	QString fname = FileUtil::getSaveFileName(this,
 						  tr("Export message history"),
 						  s + ".txt",
@@ -491,17 +1009,23 @@ void HistoryDlg::exportHistory()
 		us  = d->psi->contactList()->defaultAccount()->nick();
 	}
 
-	QString id;
 	EDBHandle *h;
+	int start = 0;
 	startRequest();
+	QString paId = getCurrentAccountId();
+	int max = 0;
+	{
+		quint64 edbCnt = d->psi->edb()->eventsCount(paId, d->jid);
+		if (edbCnt > 1000) {
+			max = edbCnt / 1000;
+			if ((edbCnt % 1000) != 0)
+				++max;
+			showProgress(max);
+ 		}
+	}
 	while(1) {
 		h = new EDBHandle(edb);
-		if(id.isEmpty()) {
-			h->getOldest(d->jid, 1000);
-		}
-		else {
-			h->get(d->jid, id, EDB::Forward, 1000);
-		}
+		h->get(paId, d->jid, QDateTime(), EDB::Forward, start, 1000);
 		while(h->busy()) {
 			qApp->processEvents();
 		}
@@ -512,19 +1036,24 @@ void HistoryDlg::exportHistory()
 		// events are in forward order
 		for(int i = 0; i < cnt; ++i) {
 			EDBItemPtr item = r.value(i);
-			id = item->nextId();
 			PsiEvent::Ptr e(item->event());
 			QString txt;
 
 			QString ts = e->timeStamp().toString(Qt::LocalDate);
 
 			QString nick;
-			if(e->originLocal())
-				nick = us;
-			else if (them.isEmpty())
-				nick = e->from().full();
-			else
-				nick = them;
+			if(e->originLocal()) {
+				if (e->account())
+					nick = e->account()->nick();
+				else
+					nick = tr("deleted");
+			}
+ 			else {
+				if (!them.isEmpty())
+					nick = them;
+				else
+					nick = e->from().full();
+			}
 
 			if(e->type() == PsiEvent::Message) {
 				MessageEvent::Ptr me = e.staticCast<MessageEvent>();
@@ -546,10 +1075,14 @@ void HistoryDlg::exportHistory()
 		}
 		delete h;
 
+		if (max > 0)
+			incrementProgress();
+
 		// done!
-		if(cnt == 0 || id.isEmpty()) {
+		if(cnt == 0)
 			break;
-		}
+
+		start += 1000;
 	}
 	f.close();
 	stopRequest();
@@ -584,93 +1117,30 @@ void HistoryDlg::doMenu()
 
 void HistoryDlg::edbFinished()
 {
-	setButtons(false);
 	stopRequest();
 
 	EDBHandle* h = qobject_cast<EDBHandle*>(sender());
-	if(!h) {
-		return;
-	}
-
-	const EDBResult r = h->result();
-	if (h->lastRequestType() == EDBHandle::Read)
-	{
-		if (r.count() > 0)
-		{
-			if (d->reqType == TypeLatest || d->reqType == TypePrevious)
-			{
-				// events are in backward order
-				// first entry is the end event
-				EDBItemPtr it = r.first();
-				d->id_end = it->id();
-				d->id_next = it->nextId();
-				// last entry is the begin event
-				it = r.last();
-				d->id_begin = it->id();
-				d->id_prev = it->prevId();
-				displayResult(r, EDB::Forward);
-				setButtons();
-			}
-			else if (d->reqType == TypeEarliest || d->reqType == TypeNext || d->reqType == TypeDate)
-			{
-				// events are in forward order
-				// last entry is the end event
-				EDBItemPtr it = r.last();
-				d->id_end = it->id();
-				d->id_next = it->nextId();
-				// first entry is the begin event
-				it = r.first();
-				d->id_begin = it->id();
-				d->id_prev = it->prevId();
-				displayResult(r, EDB::Backward);
-				setButtons();
-			}
-			else if (d->reqType == TypeFindOldest)
-			{
-				QString str = ui_.searchField->text();
-				if (str.isEmpty())
-				{
-					getLatest();
-				}
-				else
-				{
-					d->reqType = TypeFind;
-					d->findStr = str;
-					EDBItemPtr ei = r.first();
-					startRequest();
-					getEDBHandle()->find(str, d->jid, ei->id(), EDB::Forward);
-					setButtons();
-				}
-			}
-			else if (d->reqType == TypeFind)
-			{
-				displayResult(r, EDB::Forward);
-				highlightBlocks(ui_.searchField->text());
-			}
-
-		}
-		else
-		{
-			ui_.msgLog->clear();
-		}
-	}
 	delete h;
 }
 
 void HistoryDlg::setButtons()
 {
-	ui_.buttonPrevious->setEnabled(!d->id_prev.isEmpty());
-	ui_.buttonNext->setEnabled(!d->id_next.isEmpty());
-	ui_.buttonEarliest->setEnabled(!d->id_prev.isEmpty());
-	ui_.buttonLastest->setEnabled(!d->id_next.isEmpty());
-}
-
-void HistoryDlg::setButtons(bool act)
-{
-	ui_.buttonPrevious->setEnabled(act);
-	ui_.buttonNext->setEnabled(act);
-	ui_.buttonEarliest->setEnabled(act);
-	ui_.buttonLastest->setEnabled(act);
+	bool can_backward = displayProxy->canBackward();
+	bool can_forward  = displayProxy->canForward();
+	ui_.buttonPrevious->setEnabled(can_backward);
+	ui_.buttonNext->setEnabled(can_forward);
+	ui_.buttonEarliest->setEnabled(can_backward);
+	ui_.buttonLastest->setEnabled(can_forward);
+}
+
+void HistoryDlg::setLooks(QWidget *w)
+{
+	QPalette pal = w->palette();
+	pal.setColor(QPalette::Inactive, QPalette::HighlightedText,
+		pal.color(QPalette::Active, QPalette::HighlightedText));
+	pal.setColor(QPalette::Inactive, QPalette::Highlight,
+		pal.color(QPalette::Active, QPalette::Highlight));
+	w->setPalette(pal);
 }
 
 void HistoryDlg::refresh()
@@ -682,41 +1152,33 @@ void HistoryDlg::refresh()
 
 void HistoryDlg::getLatest()
 {
-	d->reqType = TypeLatest;
 	startRequest();
-	getEDBHandle()->getLatest(d->jid, 50);
+	displayProxy->displayLatest(getCurrentAccountId(), d->jid);
 }
 
 void HistoryDlg::getEarliest()
 {
-	d->reqType = TypeEarliest;
 	startRequest();
-	getEDBHandle()->getOldest(d->jid, 50);
+	displayProxy->displayEarliest(getCurrentAccountId(), d->jid);
 }
 
 void HistoryDlg::getPrevious()
 {
-	d->reqType = TypePrevious;
-	ui_.buttonPrevious->setEnabled(false);
-	getEDBHandle()->get(d->jid, d->id_prev, EDB::Backward, 50);
+	startRequest();
+	displayProxy->displayPrevious();
 }
 
 void HistoryDlg::getNext()
 {
-	d->reqType = TypeNext;
-	ui_.buttonNext->setEnabled(false);
-	getEDBHandle()->get(d->jid, d->id_next, EDB::Forward, 50);
+	startRequest();
+	displayProxy->displayNext();
 }
 
 void HistoryDlg::getDate()
 {
-	const QDate date = ui_.calendar->selectedDate();
-	d->reqType = TypeDate;
-	d->date = date;
-	QDateTime first (d->date);
-	QDateTime last = first.addDays(1);
+	QDateTime ts(ui_.calendar->selectedDate());
 	startRequest();
-	getEDBHandle()->getByDate(d->jid, first, last);
+	displayProxy->displayFromDate(getCurrentAccountId(), d->jid, ts);
 }
 
 void HistoryDlg::removedContact(PsiContact *pc)
@@ -731,7 +1193,7 @@ void HistoryDlg::removedContact(PsiConta
 	contactListModel()->updateContacts(d->psi, getCurrentAccountId());
 
 	if (!cid.isEmpty() && !selectContact(QStringList(cid)))
-		ui_.msgLog->clear();
+		resetWidgets();
 }
 
 void HistoryDlg::optionUpdated(const QString &option)
@@ -742,13 +1204,13 @@ void HistoryDlg::optionUpdated(const QSt
 	} else
 #endif
 	if(option == "options.ui.look.colors.messages.sent")
-			d->sentColor = PsiOptions::instance()->getOption(option).toString();
+			displayProxy->setSentColor(PsiOptions::instance()->getOption(option).toString());
 	else if(option == "options.ui.look.colors.messages.received")
-			d->receivedColor = PsiOptions::instance()->getOption(option).toString();
+			displayProxy->setReceivedColor(PsiOptions::instance()->getOption(option).toString());
 	else if(option == "options.ui.emoticons.use-emoticons")
-			d->emoticons  = PsiOptions::instance()->getOption(option).toBool();
+			displayProxy->setEmoticonsFlag(PsiOptions::instance()->getOption(option).toBool());
 	else if(option == "options.ui.chat.legacy-formatting")
-			d->formatting = PsiOptions::instance()->getOption(option).toBool();
+			displayProxy->setFormattingFlag(PsiOptions::instance()->getOption(option).toBool());
 }
 #ifndef HAVE_X11
 void HistoryDlg::autoCopy()
@@ -758,42 +1220,29 @@ void HistoryDlg::autoCopy()
 	}
 }
 #endif
-void HistoryDlg::displayResult(const EDBResult r, int direction, int max)
+void HistoryDlg::viewUpdated()
 {
-	int i  = (direction == EDB::Forward) ? r.count() - 1 : 0;
-	int at = 0;
-	ui_.msgLog->clear();
-	PsiAccount *pa = d->pa ? d->pa : d->psi->contactList()->defaultAccount();
-	QString nick = TextUtil::plain2rich(pa->nick());
-	while (i >= 0 && i <= r.count() - 1 && (max == -1 ? true : at < max))
-	{
-		EDBItemPtr item = r.value(i);
-		PsiEvent::Ptr e(item->event());
-		if (e->type() == PsiEvent::Message)
-		{
-			QString from = getNick(e->account(), e->from());
-			MessageEvent::Ptr me = e.staticCast<MessageEvent>();
-			QString msg = me->message().body();
-			msg = TextUtil::linkify(TextUtil::plain2rich(msg));
-
-			if (d->emoticons)
-				msg = TextUtil::emoticonify(msg);
-			if (d->formatting)
-				msg = TextUtil::legacyFormat(msg);
-
-			if (me->originLocal())
-				msg = "<span style='color:"+d->sentColor+"'>" + me->timeStamp().toString("[dd.MM.yyyy hh:mm:ss]")+" &lt;"+ nick +"&gt; " + msg + "</span>";
-			else
-				msg = "<span style='color:"+d->receivedColor+"'>" + me->timeStamp().toString("[dd.MM.yyyy hh:mm:ss]") + " &lt;" +  TextUtil::plain2rich(from) + "&gt; " + msg + "</span>";
-
-			ui_.msgLog->appendText(msg);
-		}
+	highlightBlocks();
+	setButtons();
+	stopRequest();
+}
 
-		++at;
-		i += (direction == EDB::Forward) ? -1 : +1;
-	}
+void HistoryDlg::showFoundResult(int rows)
+{
+	if (rows == 0)
+		stopRequest();
+	if (searchProxy->totalFound() == 0)
+		updateSearchHint();
+}
 
-	ui_.msgLog->verticalScrollBar()->setValue(ui_.msgLog->verticalScrollBar()->maximum());
+void HistoryDlg::updateSearchHint()
+{
+	int cnt = searchProxy->totalFound();
+	if (cnt > 0)
+		ui_.searchResult->setText(tr("%1 of %2 matches").arg(searchProxy->cursorPosition()).arg(cnt));
+	else
+		ui_.searchResult->setText(tr("No matches found"));
+	ui_.searchResult->setVisible(true);
 }
 
 UserListItem* HistoryDlg::currentUserListItem() const
@@ -811,6 +1260,7 @@ void HistoryDlg::startRequest()
 	}
 	saveFocus();
 	setEnabled(false);
+	qApp->processEvents();
 }
 
 void HistoryDlg::stopRequest()
@@ -818,13 +1268,22 @@ void HistoryDlg::stopRequest()
 	if(ui_.busy->isActive()) {
 		ui_.busy->stop();
 	}
+	ui_.progressBar->setVisible(false);
 	setEnabled(true);
 	restoreFocus();
-#ifdef Q_OS_MAC
-	// To workaround a Qt bug
-	// https://bugreports.qt-project.org/browse/QTBUG-26351
-	setFocus();
-#endif
+	restoreFocus();
+}
+
+void HistoryDlg::showProgress(int max)
+{
+	ui_.progressBar->setValue(0);
+	ui_.progressBar->setMaximum(max);
+	ui_.progressBar->setVisible(true);
+}
+
+void HistoryDlg::incrementProgress()
+{
+	ui_.progressBar->setValue(ui_.progressBar->value() + 1);
 }
 
 EDBHandle* HistoryDlg::getEDBHandle()
@@ -845,21 +1304,3 @@ HistoryContactListModel *HistoryDlg::con
 {
 	return _contactListModel;
 }
-
-QString HistoryDlg::getNick(PsiAccount *pa, const Jid &jid) const
-{
-	QString from;
-	if (pa)
-	{
-		UserListItem *u = pa->findFirstRelevant(jid);
-		if (u && !u->name().trimmed().isEmpty())
-			from = u->name().trimmed();
-	}
-	if (from.isEmpty())
-	{
-		from = jid.resource().trimmed(); // for offline conferences
-		if (from.isEmpty())
-			from = jid.node();
-	}
-	return from;
-}
--- git.orig/src/historydlg.h
+++ git/src/historydlg.h
@@ -34,6 +34,118 @@ namespace XMPP {
 	class Jid;
 }
 
+class DisplayProxy;
+
+class SearchProxy : public QObject
+{
+	Q_OBJECT
+
+public:
+	SearchProxy(PsiCon *p, DisplayProxy *d);
+	void find(const QString &str, const QString &acc_id, const XMPP::Jid &jid, int dir);
+	int totalFound() const { return total_found; }
+	int cursorPosition() const { return general_pos; }
+
+private slots:
+	void handleResult();
+
+signals:
+	void found(int);
+	void needRequest();
+
+private:
+	struct Position {
+		QDateTime date;
+		int       num;
+	} position;
+	int general_pos;
+	int total_found;
+	void reset();
+	EDBHandle* getEDBHandle();
+	void movePosition(int dir);
+	int  invertSearchPosition(const Position &pos, int dir);
+	void handleFoundData(const EDBResult &r);
+	void handlePadding(const EDBResult &r);
+
+private:
+	bool active;
+	int direction;
+	QString s_string;
+	QHash<uint, int> map;
+	QVector<Position> list;
+	PsiCon *psi;
+	DisplayProxy *dp;
+	QString acc_;
+	XMPP::Jid jid_;
+	enum RequestType { ReqFind, ReqPadding };
+	RequestType reqType;
+
+};
+
+class DisplayProxy : public QObject
+{
+	Q_OBJECT
+
+public:
+	DisplayProxy(PsiCon *p, PsiTextView *v);
+
+	void setEmoticonsFlag(bool f) { emoticons = f; }
+	void setFormattingFlag(bool f) { formatting = f; }
+	void setSentColor(const QString &c) { sentColor = c; }
+	void setReceivedColor(const QString &c) { receivedColor = c; }
+
+	bool canBackward() const { return can_backward; }
+	bool canForward() const { return can_forward; }
+
+	void displayEarliest(const QString &acc_id, const XMPP::Jid &jid);
+	void displayLatest(const QString &acc_id, const XMPP::Jid &jid);
+	void displayFromDate(const QString &acc_id, const XMPP::Jid &jid, const QDateTime date);
+	void displayNext();
+	void displayPrevious();
+	bool moveSearchCursor(int dir, int n);
+	void displayWithSearchCursor(const QString &acc_id, const XMPP::Jid &jid, QDateTime start, int dir, const QString &s_str, int num);
+
+private slots:
+	void handleResult();
+
+signals:
+	void updated();
+	void searchCursorMoved();
+
+private:
+	EDBHandle* getEDBHandle();
+	void resetSearch();
+	void updateQueryParams(int dir, int increase, QDateTime date = QDateTime());
+	void displayResult(const EDBResult &r, int dir);
+	QString getNick(PsiAccount *pa, const XMPP::Jid &jid) const;
+
+private:
+	QString acc_;
+	XMPP::Jid jid_;
+	struct {
+		int       direction;
+		int       offset;
+		QDateTime date;
+	} queryParams;
+	struct {
+		int     searchPos;
+		int     cursorPos;
+		int     searchDir;
+		QString searchString;
+	} searchParams;
+	enum RequestType { ReqNone, ReqDate, ReqEarliest, ReqLatest, ReqNext, ReqPrevious };
+	RequestType reqType;
+	PsiCon *psi;
+	PsiTextView *viewWid;
+	bool formatting;
+	bool emoticons;
+	bool can_backward;
+	bool can_forward;
+	QString sentColor;
+	QString receivedColor;
+
+};
+
 class HistoryDlg : public AdvancedWidget<QDialog>
 {
 	Q_OBJECT
@@ -42,17 +154,6 @@ public:
 	HistoryDlg(const XMPP::Jid&, PsiAccount*);
 	virtual ~HistoryDlg();
 
-	enum RequestType {
-		TypeNone = 0,
-		TypeNext,
-		TypePrevious,
-		TypeLatest,
-		TypeEarliest,
-		TypeFind,
-		TypeFindOldest,
-		TypeDate
-	};
-
 private slots:
 	void openSelectedContact();
 	void getLatest();
@@ -63,7 +164,7 @@ private slots:
 	void refresh();
 	void findMessages();
 	void edbFinished();
-	void highlightBlocks(const QString text);
+	void highlightBlocks();
 	void changeAccount(const QString accountName);
 	void removeHistory();
 	void exportHistory();
@@ -74,6 +175,10 @@ private slots:
 #ifndef HAVE_X11
 	void autoCopy();
 #endif
+	void viewUpdated();
+	void showFoundResult(int rows);
+	void updateSearchHint();
+	void startRequest();
 
 protected:
 	bool eventFilter(QObject *, QEvent *);
@@ -81,13 +186,14 @@ protected:
 private:
 	void setFilterModeEnabled(bool enable);
 	void setButtons();
-	void setButtons(bool act);
-	void displayResult(const EDBResult , int, int max=-1);
+	void setLooks(QWidget *w);
 	QFont fontForOption(const QString& option);
+	void resetWidgets();
 	void listAccounts();
 	UserListItem* currentUserListItem() const;
-	void startRequest();
 	void stopRequest();
+	void showProgress(int max);
+	void incrementProgress();
 	bool selectContact(const QString &accId, const Jid &jid);
 	bool selectContact(const QStringList &ids);
 	void selectDefaultContact(const QModelIndex &prefer_parent = QModelIndex(), int prefer_row = 0);
@@ -96,14 +202,15 @@ private:
 	EDBHandle* getEDBHandle();
 	QString getCurrentAccountId() const;
 	HistoryContactListModel *contactListModel();
-	QString getNick(PsiAccount *pa, const XMPP::Jid &jid) const;
 
 	class Private;
 	Private *d;
 	Ui::HistoryDlg ui_;
-	QStringList jids_;
 	HistoryContactListModel *_contactListModel;
 	QWidget *lastFocus;
+	DisplayProxy *displayProxy;
+	SearchProxy *searchProxy;
+
 };
 
 #endif
--- git.orig/src/historyimp.cpp
+++ git/src/historyimp.cpp
@@ -0,0 +1,304 @@
+/*
+ * historyimp.cpp
+ * Copyright (C) 2011   Aleksey Andreev
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <QDir>
+#include <QTimer>
+#include <QMessageBox>
+#include <QLayout>
+
+#include "historyimp.h"
+#include "edbsqlite.h"
+#include "edbflatfile.h"
+#include "applicationinfo.h"
+#include "psicontactlist.h"
+#include "psiaccount.h"
+#include "psicontact.h"
+
+HistoryImport::HistoryImport(PsiCon *psi) : QObject(),
+	psi_(psi),
+	srcEdb(NULL),
+	dstEdb(NULL),
+	hErase(NULL),
+	hRead(NULL),
+	hWrite(NULL),
+	active(false),
+	result_(ResultNone),
+	recordsCount(0),
+	dlg(NULL)
+{
+}
+
+HistoryImport::~HistoryImport()
+{
+	clear();
+}
+
+bool HistoryImport::isNeeded()
+{
+	bool res = false;
+	EDBSqLite *stor = static_cast<EDBSqLite *>(psi_->edb());
+	if (!stor->getStorageParam("import_start").isEmpty()) {
+		EDB *src = stor->mirror();
+		if (!src)
+			src = new EDBFlatFile(psi_);
+		//if (sou && sou->eventsCount(QString(), XMPP::Jid()) != 0)
+		if (!src->contacts(QString(), EDB::Contact).isEmpty())
+			res = true;
+		else
+			stor->setStorageParam("import_start", QString());
+		if (src != stor->mirror())
+			delete src;
+	}
+	return res;
+}
+
+void HistoryImport::clear()
+{
+	if (dstEdb) {
+		((EDBSqLite *)dstEdb)->setInsertingMode(EDBSqLite::Normal);
+		((EDBSqLite *)dstEdb)->setMirror(new EDBFlatFile(psi_));
+	}
+	if (hErase) {
+		delete hErase;
+		hErase = NULL;
+	}
+	if (hRead) {
+		delete hRead;
+		hRead = NULL;
+	}
+	if (srcEdb) {
+		delete srcEdb;
+		srcEdb = NULL;
+	}
+	if (hWrite) {
+		delete hWrite;
+		hWrite = NULL;
+	}
+	if (dlg) {
+		delete dlg;
+		dlg = NULL;
+	}
+}
+
+int HistoryImport::exec()
+{
+	active = true;
+
+	dstEdb = psi_->edb();
+	((EDBSqLite *)dstEdb)->setMirror(NULL);
+	((EDBSqLite *)dstEdb)->setInsertingMode(EDBSqLite::Import);
+
+	dstEdb->setStorageParam("import_start", "yes");
+
+	if (!srcEdb)
+		srcEdb = new EDBFlatFile(psi_);
+
+	foreach (const EDB::ContactItem &ci, srcEdb->contacts(QString(), EDB::Contact)) {
+		const XMPP::Jid &jid = ci.jid;
+		QStringList accIds;
+		foreach (PsiAccount *acc, psi_->contactList()->accounts()) {
+			foreach (PsiContact *contact, acc->contactList()) {
+				if (contact->jid() == jid)
+					accIds.append(acc->id());
+			}
+		}
+		if (accIds.isEmpty()) {
+			PsiAccount *pa = psi_->contactList()->defaultAccount();
+			if (pa)
+				accIds.append(pa->id());
+			else
+				accIds.append(psi_->contactList()->accounts().first()->id());
+		}
+		importList.append(ImportItem(accIds, jid));
+	}
+
+	if (importList.isEmpty())
+		stop(ResultNormal);
+	else
+		showDialog();
+
+	return result_;
+}
+
+void HistoryImport::stop(int reason)
+{
+	stopTime = QDateTime::currentDateTime();
+	result_ = reason;
+	if (reason == ResultNormal) {
+		dstEdb->setStorageParam("import_start", QString());
+		int sec = importDuration();
+		int min = sec / 60;
+		sec = sec % 60;
+		qWarning(QString("Import is finished. Duration is %1 min. %2 sec.").arg(min).arg(sec).toLatin1());
+	}
+	else if (reason == ResultCancel)
+		qWarning("Import canceled");
+	else
+		qWarning("Import error");
+
+	active = false;
+	emit finished(reason);
+}
+
+int HistoryImport::importDuration()
+{
+	return startTime.secsTo(stopTime);
+}
+
+void HistoryImport::readFromFiles()
+{
+	if (!active)
+		return;
+	if (hWrite != NULL) {
+		if (!hWrite->writeSuccess()) {
+			stop(ResultError); // Write error
+			return;
+		}
+	}
+	else if (hErase != NULL && !hErase->writeSuccess()) {
+		stop(ResultError);
+		return;
+	}
+	if (importList.isEmpty()) {
+		stop(ResultNormal);
+		return;
+	}
+	if (hRead == NULL) {
+		hRead = new EDBHandle(srcEdb);
+		connect(hRead, SIGNAL(finished()), this, SLOT(writeToSqlite()));
+	}
+
+	const ImportItem &item = importList.first();
+	int start = item.startNum;
+	if (start == 0)
+		qWarning(QString("Importing %1").arg(JIDUtil::toString(item.jid, true)).toLatin1());
+	--recordsCount;
+	if (dlg && (recordsCount % 100) == 0)
+		progressBar->setValue(progressBar->value() + 1);
+	hRead->get(item.accIds.first(), item.jid, QDateTime(), EDB::Forward, start, 1);
+}
+
+void HistoryImport::writeToSqlite()
+{
+	if (!active)
+		return;
+	const EDBResult r = hRead->result();
+	if (hRead->lastRequestType() != EDBHandle::Read || r.size() > 1) {
+		stop(ResultError);
+		return;
+	}
+	if (r.isEmpty()) {
+		importList.first().accIds.removeFirst();
+		if (importList.first().accIds.isEmpty())
+			importList.removeFirst();
+		QTimer::singleShot(0, this, SLOT(readFromFiles()));
+		return;
+	}
+	if (hWrite == NULL) {
+		hWrite = new EDBHandle(dstEdb);
+		connect(hWrite, SIGNAL(finished()), this, SLOT(readFromFiles()));
+	}
+	EDBItemPtr it = r.first();
+	ImportItem &item = importList.first();
+	hWrite->append(item.accIds.first(), item.jid, it->event(), EDB::Contact);
+	item.startNum += 1;
+}
+
+void HistoryImport::showDialog()
+{
+	dlg = new QDialog();
+	dlg->setModal(true);
+	dlg->setWindowTitle(tr("Psi+ Import history"));
+	QVBoxLayout *mainLayout = new QVBoxLayout(dlg);
+	stackedWidget = new QStackedWidget(dlg);
+
+	QWidget *page1 = new QWidget();
+	QGridLayout *page1Layout = new QGridLayout(page1);
+	QLabel *lbMessage = new QLabel(page1);
+	lbMessage->setWordWrap(true);
+	lbMessage->setText(tr("Found %1 files for import.\nContinue?").arg(importList.size()));
+	page1Layout->addWidget(lbMessage, 0, 0, 1, 1);
+	stackedWidget->addWidget(page1);
+
+	QWidget *page2 = new QWidget();
+	QHBoxLayout *page2Layout = new QHBoxLayout(page2);
+	QGridLayout *page2GridLayout = new QGridLayout();
+	page2GridLayout->addWidget(new QLabel(tr("Status:"), page2), 0, 0, 1, 1);
+	lbStatus = new QLabel(page2);
+	page2GridLayout->addWidget(lbStatus, 0, 1, 1, 1);
+	page2GridLayout->addWidget(new QLabel(tr("Progress:"), page2), 1, 0, 1, 1);
+	progressBar = new QProgressBar(page2);
+	progressBar->setMaximum(1);
+	progressBar->setValue(0);
+	page2GridLayout->addWidget(progressBar, 1, 1, 1, 1);
+	page2Layout->addLayout(page2GridLayout);
+	stackedWidget->addWidget(page2);
+
+	mainLayout->addWidget(stackedWidget);
+	QHBoxLayout *buttonsLayout = new QHBoxLayout();
+	QSpacerItem *buttonsSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
+	buttonsLayout->addItem(buttonsSpacer);
+	btnOk = new QPushButton(dlg);
+	connect(btnOk, SIGNAL(clicked()), this, SLOT(start()));
+	btnOk->setText(tr("Ok"));
+	buttonsLayout->addWidget(btnOk);
+	QPushButton *btnCancel = new QPushButton(dlg);
+	connect(btnCancel, SIGNAL(clicked()), this, SLOT(cancel()));
+	btnCancel->setText(tr("Exit"));
+	buttonsLayout->addWidget(btnCancel);
+	mainLayout->addLayout(buttonsLayout);
+
+	dlg->adjustSize();
+	dlg->exec();
+}
+
+void HistoryImport::start()
+{
+	qWarning("Import start");
+	startTime = QDateTime::currentDateTime();
+	btnOk->setEnabled(false);
+	stackedWidget->setCurrentIndex(1);
+
+	lbStatus->setText(tr("Counting records"));
+	qApp->processEvents();
+	recordsCount = srcEdb->eventsCount(QString(), XMPP::Jid());
+	int max = recordsCount / 100;
+	if ((recordsCount % 100) != 0)
+		++max;
+	progressBar->setMaximum(max);
+	progressBar->setValue(0);
+
+	lbStatus->setText(tr("Import"));
+	hErase = new EDBHandle(dstEdb);
+	connect(hErase, SIGNAL(finished()), this, SLOT(readFromFiles()));
+	hErase->erase(QString(), QString());
+	while (active)
+		qApp->processEvents();
+	if (result_ == ResultNormal)
+		dlg->accept();
+	else
+		lbStatus->setText(tr("Error"));
+}
+
+void HistoryImport::cancel()
+{
+	stop();
+	dlg->reject();
+}
--- git.orig/src/historyimp.h
+++ git/src/historyimp.h
@@ -0,0 +1,91 @@
+/*
+ * historyimp.h
+ * Copyright (C) 2011   Aleksey Andreev
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef HISTORYIMP_H
+#define HISTORYIMP_H
+
+#include <QObject>
+#include <QDialog>
+#include <QLabel>
+#include <QProgressBar>
+#include <QStackedWidget>
+#include <QPushButton>
+
+#include "xmpp/jid/jid.h"
+#include "jidutil.h"
+#include "psicon.h"
+#include "eventdb.h"
+
+struct ImportItem
+{
+	QStringList   accIds;
+	XMPP::Jid     jid;
+	int           startNum;
+	ImportItem(const QStringList &ids, const XMPP::Jid &j) { accIds = ids; jid = j; startNum = 0; }
+};
+
+class HistoryImport : public QObject
+{
+	Q_OBJECT
+
+public:
+	enum {ResultNone, ResultNormal, ResultCancel, ResultError};
+	HistoryImport(PsiCon *psi);
+	~HistoryImport();
+	bool isNeeded();
+	int exec();
+	int importDuration();
+
+private:
+	PsiCon *psi_;
+	QList<ImportItem> importList;
+	EDB *srcEdb;
+	EDB *dstEdb;
+	EDBHandle *hErase;
+	EDBHandle *hRead;
+	EDBHandle *hWrite;
+	QDateTime startTime;
+	QDateTime stopTime;
+	bool active;
+	int result_;
+	quint64 recordsCount;
+	QDialog *dlg;
+	QLabel *lbStatus;
+	QProgressBar *progressBar;
+	QStackedWidget *stackedWidget;
+	QPushButton *btnOk;
+
+private:
+	void clear();
+	void showDialog();
+
+private slots:
+	void readFromFiles();
+	void writeToSqlite();
+	void start();
+	void stop(int reason = ResultCancel);
+	void cancel();
+
+signals:
+	void finished(int);
+
+};
+
+#endif
--- git.orig/src/history.ui
+++ git/src/history.ui
@@ -151,35 +151,60 @@
      <item>
       <layout class="QVBoxLayout" name="verticalLayout_3">
        <item>
-        <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <layout class="QHBoxLayout" name="horizontalLayout_5">
          <item>
-          <widget class="QLineEdit" name="searchField">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>Search:</string>
            </property>
           </widget>
          </item>
          <item>
-          <widget class="QToolButton" name="tb_find">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>27</width>
-             <height>27</height>
-            </size>
-           </property>
-           <property name="text">
-            <string/>
-           </property>
-          </widget>
+          <layout class="QVBoxLayout" name="verticalLayout_4">
+           <item>
+            <layout class="QHBoxLayout" name="horizontalLayout_6">
+             <item>
+              <widget class="QLineEdit" name="searchField">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QToolButton" name="tbFindForward">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="maximumSize">
+                <size>
+                 <width>27</width>
+                 <height>27</height>
+                </size>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QToolButton" name="tbFindBackward"/>
+             </item>
+            </layout>
+           </item>
+           <item>
+            <widget class="QLabel" name="searchResult">
+             <property name="frameShape">
+              <enum>QFrame::StyledPanel</enum>
+             </property>
+            </widget>
+           </item>
+          </layout>
          </item>
         </layout>
        </item>
@@ -296,6 +321,16 @@
        </property>
       </spacer>
      </item>
+     <item>
+      <widget class="QProgressBar" name="progressBar">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>1</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+      </widget>
+     </item>
     </layout>
    </item>
   </layout>
@@ -316,13 +351,11 @@
  <tabstops>
   <tabstop>contactList</tabstop>
   <tabstop>calendar</tabstop>
-  <tabstop>searchField</tabstop>
   <tabstop>buttonEarliest</tabstop>
   <tabstop>buttonPrevious</tabstop>
   <tabstop>buttonNext</tabstop>
   <tabstop>buttonLastest</tabstop>
   <tabstop>accountsBox</tabstop>
-  <tabstop>tb_find</tabstop>
   <tabstop>msgLog</tabstop>
  </tabstops>
  <resources/>
--- git.orig/src/messageview.cpp
+++ git/src/messageview.cpp
@@ -32,7 +32,8 @@ MessageView::MessageView(Type t) :
     _flags(0),
 	_status(0),
 	_statusPriority(0),
-	_dateTime(QDateTime::currentDateTime())
+	_dateTime(QDateTime::currentDateTime()),
+	_carbon(XMPP::Message::NoCarbon)
 {
 }
 
--- git.orig/src/messageview.h
+++ git/src/messageview.h
@@ -24,6 +24,8 @@
 #include <QDateTime>
 #include <QVariantMap>
 
+#include "xmpp_message.h"
+
 #if QT_VERSION < QT_VERSION_CHECK(5,7,0)
 # define SET_QFLAG(flags, flag, state) if (state) flags |= flag; else flags &= ~flag
 #else
@@ -123,6 +125,8 @@ public:
 	inline QMap<QString, QString> urls() const { return _urls; }
 	inline void setReplaceId(const QString &id) { _replaceId = id; }
 	inline const QString &replaceId() const { return _replaceId; }
+	inline void setCarbonDirection(XMPP::Message::CarbonDir c) {_carbon = c; }
+	inline XMPP::Message::CarbonDir carbonDirection() const { return _carbon; }
 
 	QVariantMap toVariantMap(bool isMuc, bool formatted = false) const;
 
@@ -139,6 +143,7 @@ private:
 	QDateTime _dateTime;
 	QMap<QString, QString> _urls;
 	QString _replaceId;
+	XMPP::Message::CarbonDir _carbon;
 };
 
 Q_DECLARE_OPERATORS_FOR_FLAGS(MessageView::Flags)
--- git.orig/src/psiaccount.cpp
+++ git/src/psiaccount.cpp
@@ -4730,11 +4730,12 @@ void PsiAccount::dj_sendMessage(const Me
 
 	// don't log groupchat, private messages, or encrypted messages
 	if(log) {
-		if(m.type() != "groupchat" && m.xencrypted().isEmpty() && !findGCContact(m.to())) {
+		if(m.type() != "groupchat" && m.xencrypted().isEmpty()/* && !findGCContact(m.to())*/) {
+			int type = findGCContact(m.to()) ? EDB::GroupChatContact : EDB::Contact;
 			MessageEvent::Ptr me(new MessageEvent(m, this));
 			me->setOriginLocal(true);
 			me->setTimeStamp(QDateTime::currentDateTime());
-			logEvent(m.to(), me);
+			logEvent(m.to(), me, type);
 		}
 	}
 
@@ -5079,7 +5080,7 @@ void PsiAccount::handleEvent(const PsiEv
 
 			// don't log private messages
 			if (!found &&
-				!findGCContact(e->from()) &&
+				//!findGCContact(e->from()) &&
 				!(e->type() == PsiEvent::Message &&
 				 e.staticCast<MessageEvent>()->message().body().isEmpty()))
 			{
@@ -5100,8 +5101,8 @@ void PsiAccount::handleEvent(const PsiEv
 							chatJid = m.to();
 						}
 					}
-
-					logEvent(chatJid, e);
+					int type = findGCContact(chatJid) ? EDB::GroupChatContact : EDB::Contact;
+					logEvent(chatJid, e, type);
 				}
 			}
 		}
@@ -5813,14 +5814,16 @@ void PsiAccount::groupChatMessagesRead(c
 }
 #endif
 
-void PsiAccount::logEvent(const Jid &j, const PsiEvent::Ptr &e)
+void PsiAccount::logEvent(const Jid &j, const PsiEvent::Ptr &e, int type)
 {
 	if (!d->acc.opt_log)
 		return;
+	if (type == EDB::GroupChatContact && !PsiOptions::instance()->getOption("options.history.store-muc-private").toBool())
+		return;
 
 	EDBHandle *h = new EDBHandle(d->psi->edb());
 	connect(h, SIGNAL(finished()), SLOT(edb_finished()));
-	h->append(j, e);
+	h->append(id(), j, e, type);
 }
 
 void PsiAccount::edb_finished()
@@ -6279,7 +6282,7 @@ void PsiAccount::pgp_encryptFinished()
 			MessageEvent::Ptr me(new MessageEvent(m, this));
 			me->setOriginLocal(true);
 			me->setTimeStamp(QDateTime::currentDateTime());
-			logEvent(m.to(), me);
+			logEvent(m.to(), me, EDB::Contact);
 		}
 
 		Message mwrap;
--- git.orig/src/psiaccount.h
+++ git/src/psiaccount.h
@@ -530,7 +530,7 @@ private:
 	void simulateRosterOffline();
 	void cpUpdate(const UserListItem &, const QString &rname="", bool fromPresence=false);
 	UserListItem* addUserListItem(const Jid& jid, const QString& nick="");
-	void logEvent(const Jid &, const PsiEvent::Ptr &);
+	void logEvent(const Jid &, const PsiEvent::Ptr &, int);
 	void queueEvent(const PsiEvent::Ptr &e, ActivationType activationType);
 	void openNextEvent(const UserListItem &, ActivationType activationType);
 	void updateReadNext(const Jid &);
--- git.orig/src/psichatdlg.cpp
+++ git/src/psichatdlg.cpp
@@ -925,7 +925,7 @@ bool PsiChatDlg::isEncryptionEnabled() c
 
 void PsiChatDlg::appendSysMsg(const QString &str)
 {
-	chatView()->dispatchMessage(MessageView::fromHtml(str, MessageView::System));
+	dispatchMessage(MessageView::fromHtml(str, MessageView::System));
 }
 
 ChatView* PsiChatDlg::chatView() const
--- git.orig/src/psicon.cpp
+++ git/src/psicon.cpp
@@ -62,7 +62,7 @@
 #ifdef HAVE_PGPUTIL
 #include "pgputil.h"
 #endif
-#include "edbflatfile.h"
+#include "edbsqlite.h"
 #include "proxy.h"
 #ifdef PSIMNG
 #include "psimng.h"
@@ -359,8 +359,7 @@ PsiCon::PsiCon()
 	d->ftwin = 0;
 #endif
 
-	d->edb = new EDBFlatFile(this);
-
+	d->edb = 0;
 	d->s5bServer = 0;
 	d->tuneManager = 0;
 	d->autoUpdater = 0;
@@ -381,7 +380,8 @@ PsiCon::~PsiCon()
 
 	delete d->autoUpdater;
 	delete d->actionList;
-	delete d->edb;
+	if (d->edb)
+		delete d->edb;
 	delete d->defaultMenuBar;
 	delete d->tabManager;
 	delete d->popupManager;
@@ -656,6 +656,12 @@ bool PsiCon::init()
 
 	checkAccountsEmpty();
 
+	// Import for SQLite history
+	EDBSqLite *edb = new EDBSqLite(this);
+	d->edb = edb;
+	if (!edb->init())
+		return false;
+
 	// try autologin if needed
 	foreach(PsiAccount* account, d->contactList->accounts()) {
 		account->autoLogin();
--- git.orig/src/src.pri
+++ git/src/src.pri
@@ -1,4 +1,4 @@
-QT += xml network
+QT += xml network sql
 
 greaterThan(QT_MAJOR_VERSION, 4) {
   QT += widgets multimedia concurrent
@@ -156,7 +156,9 @@ HEADERS += \
 	$$PWD/translationmanager.h \
 	$$PWD/eventdb.h \
 	$$PWD/edbflatfile.h \
+	$$PWD/edbsqlite.h \
 	$$PWD/historydlg.h \
+	$$PWD/historyimp.h \
 	$$PWD/historycontactlistmodel.h \
 	$$PWD/tipdlg.h \
 	$$PWD/searchdlg.h \
@@ -295,7 +297,9 @@ SOURCES += \
 	$$PWD/translationmanager.cpp \
 	$$PWD/eventdb.cpp \
 	$$PWD/edbflatfile.cpp \
+	$$PWD/edbsqlite.cpp \
 	$$PWD/historydlg.cpp \
+	$$PWD/historyimp.cpp \
 	$$PWD/historycontactlistmodel.cpp \
 	$$PWD/searchdlg.cpp \
 	$$PWD/registrationdlg.cpp \
--- git.orig/themes/chatview/psi/adapter.js
+++ git/themes/chatview/psi/adapter.js
@@ -271,7 +271,11 @@ function psiThemeAdapter(chat) {
                             }
                             if (!template) {
                                 data.nextOfGroup = false; //can't group w/o template
-                                template = data.local?shared.templates.sentMessage:shared.templates.receivedMessage;
+								if (data.spooled) {
+									template = shared.templates.spooledMessage;
+								} else {
+									template = data.local?shared.templates.sentMessage:shared.templates.receivedMessage;
+								}
                             }
                             break;
                         case "join":
--- git.orig/themes/chatview/psi/classic/index.html
+++ git/themes/chatview/psi/classic/index.html
@@ -80,7 +80,9 @@ function startPsiTheme(shared) {
 			receivedMessage: shared.isMuc?
 				"<div class='msg'>%icon%<span style='color:%nickcolor%'>[%time%] %sender%</span> %alertedmessage%</div>"
 				: null,
-			spooledMessage: "<div class='infmsg'>%icon%[%time%] %sender% %message%</div>",
+			spooledMessage: shared.isMuc?
+				"<div class='usertext'>%icon%<span style='color:%nickcolor%'>[%time%] %sender%</span> %message%</div>"
+				: "<div class='usertext'>%icon%<span class='%sentrec%'>[%time%] %sender%</span> %message%</div>",
 			sys: "<div class='infmsg'>%icon%%message%</div>",
 			sysMessage: "<div class='infmsg'>%icon%[%time%] *** %message%</div>",
 			sysMessageUT: "<div class='infmsg'>%icon%[%time%] *** %message%: <span class='usertext'>%usertext%</span></div>",
@@ -104,7 +106,7 @@ function startPsiTheme(shared) {
 			}
 			if (shared.cdata.mtype == "message") {
 				var template = shared.cdata.emote && shared.templates.messageNC ||
-					(shared.cdata.spooled && shared.templates.message || null);
+					(shared.cdata.spooled && shared.templates.spooledMessage || null);
 				if (template) {
 					shared.appendHtml(template.toString(), shared.cdata.local?true:null);
 					return false;
@@ -127,8 +129,7 @@ function startPsiTheme(shared) {
 					shared.cdata.message+"</span>":shared.cdata.message;
 				return shared.cdata.id? util.replaceableMessage(shared.isMuc, shared.cdata.local, shared.cdata.sender, shared.cdata.id, msg) : msg;
 			},
-			sentrec : function() {return shared.cdata.spooled?"infmsg":
-				(shared.cdata.local?"sent":"received");},
+			sentrec : function() {return shared.cdata.local?"sent":"received";},
 			nickcolor : function() {
 				return util.nickColor(shared.cdata.sender);
 			},
@@ -137,6 +138,10 @@ function startPsiTheme(shared) {
 				if (useMessageIcons) {
 					switch (shared.cdata.mtype) {
 						case "message":
+							if (shared.cdata.spooled) {
+								icon = "psi/history";
+								break;
+							}
 							icon = shared.cdata.local?(shared.cdata.awaitingReceipt?
 								"psi/notification_chat_send":"psi/notification_chat_delivery_ok")
 								: "psi/notification_chat_receive";