--- 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] <").arg(timestr) + TextUtil::escape(mv.nick()) + QString("></span> ") + inner);
+ str = icon + QString("<span style=\"color: %1\">").arg(color) + QString("[%1] <").arg(timestr) + TextUtil::escape(mv.nick()) + QString("></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]") + " <" + nick
+ + ((fAllContacts) ? QString(" -> %1").arg(TextUtil::plain2rich(from)) : QString())
+ + "> " + msg + "</span>";
+ }
+ else
+ msg = "<span style='color:" + receivedColor + "'>" + me->timeStamp().toString("[dd.MM.yyyy hh:mm:ss]") + " <"
+ + TextUtil::plain2rich(from) + "> " + 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]")+" <"+ nick +"> " + msg + "</span>";
- else
- msg = "<span style='color:"+d->receivedColor+"'>" + me->timeStamp().toString("[dd.MM.yyyy hh:mm:ss]") + " <" + TextUtil::plain2rich(from) + "> " + 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";