Commit 3b366c75 authored by Linus Jahn's avatar Linus Jahn Committed by Linus Jahn

Add SQLite database for messages and roster caching

The new SQLite3 database is saved (on Linux) under:
~/.local/share/KaidanIM/kaidan/messages.sqlite3 (This is the AppData
location from Qt)

It currently has two tables one for the roster and one for messages.
The roster table has two coloumns: jid and name. The message table
has six: author, author_resource, recipient, recipient_resource,
timestamp and message. 'recipient' and 'author' contain a bare JID
(a JID without the resource) and the resource saved in
author/recipent_resource, if available. The resource is not really
necessary, but I thought it might be cool to add e.g. some stats for
this, later.

In the Kaidan.cpp/h, I moved the handleMessageReceived part into the
MessageController, later we should also move the handle Presence
Received into a PresenceController or something as this.

I also removed the 'import harbour.kaidan 1.0' in QML, because, now
we don't have any new QML-types from Kaidan -> you can't import it.

The Chat-GUI is still very ugly, we/I should definitely imporve it
in a later commit.

Closes #55.
parent 877fd2a5
......@@ -49,7 +49,7 @@ set(EXECUTABLE_OUTPUT_PATH "bin")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
# Find packages
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Qml Quick LinguistTools)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Qml Quick Sql LinguistTools)
find_package(KF5Kirigami REQUIRED)
find_package(Swiften REQUIRED)
find_package(Boost REQUIRED)
......@@ -71,7 +71,9 @@ add_executable(${PROJECT_NAME}
src/main.cpp
src/Kaidan.cpp
src/RosterController.cpp
src/RosterItem.cpp
src/RosterModel.cpp
src/MessageController.cpp
src/MessageModel.cpp
)
#
......@@ -80,6 +82,7 @@ add_executable(${PROJECT_NAME}
target_link_libraries(${PROJECT_NAME}
Qt5::Core
Qt5::Sql
Qt5::Qml
Qt5::Quick
${SWIFTEN_LIBRARY}
......
/*
* Kaidan - Cross platform XMPP client
*
* Copyright (C) 2016 LNJ <git@lnj.li>
* Copyright (C) 2016-2017 LNJ <git@lnj.li>
* Copyright (C) 2016 Marzanna
* Copyright (C) 2016 geobra <s.g.b@gmx.de>
*
......@@ -28,16 +28,14 @@
#include <QString>
// Boost
#include <boost/bind.hpp>
#include <boost/smart_ptr/make_shared.hpp>
// Kaidan
#include "EchoPayload.h"
#include "RosterController.h"
Kaidan::Kaidan(NetworkFactories* networkFactories, QObject *parent) :
rosterController_(new RosterController()), QObject(parent)
Kaidan::Kaidan(NetworkFactories* networkFactories, QObject *parent) : QObject(parent)
{
netFactories = networkFactories;
connected = false;
rosterController = new RosterController();
//
// Restore login data
......@@ -63,15 +61,17 @@ Kaidan::~Kaidan()
{
if (connected)
{
client->disconnect();
client->removePayloadSerializer(&echoPayloadSerializer);
client->removePayloadParserFactory(&echoPayloadParserFactory);
softwareVersionResponder->stop();
delete tracer;
delete softwareVersionResponder;
delete messageController;
delete client;
}
delete rosterController_;
delete rosterController;
delete settings;
}
......@@ -86,7 +86,6 @@ void Kaidan::mainConnect()
// event handling
client->onConnected.connect(boost::bind(&Kaidan::handleConnected, this));
client->onDisconnected.connect(boost::bind(&Kaidan::handleDisconnected, this));
client->onMessageReceived.connect(boost::bind(&Kaidan::handleMessageReceived, this, _1));
client->onPresenceReceived.connect(boost::bind(&Kaidan::handlePresenceReceived, this, _1));
// Create XML tracer (console output of xmpp data)
......@@ -100,6 +99,9 @@ void Kaidan::mainConnect()
client->addPayloadParserFactory(&echoPayloadParserFactory);
client->addPayloadSerializer(&echoPayloadSerializer);
// create message controller
messageController = new MessageController(client);
// .. and connect!
client->connect();
}
......@@ -121,7 +123,8 @@ void Kaidan::handleConnected()
client->sendPresence(Presence::create("Send me a message"));
// Request the roster
rosterController_->requestRosterFromClient(client);
rosterController->requestRosterFromClient(client);
emit rosterControllerChanged();
}
void Kaidan::handleDisconnected()
......@@ -142,24 +145,14 @@ void Kaidan::handlePresenceReceived(Presence::ref presence)
}
}
void Kaidan::handleMessageReceived(Message::ref message)
RosterController* Kaidan::getRosterController()
{
// Echo back the incoming message
message->setTo(message->getFrom());
message->setFrom(JID());
if (!message->getPayload<EchoPayload>())
{
boost::shared_ptr<EchoPayload> echoPayload = boost::make_shared<EchoPayload>();
echoPayload->setMessage("This is an echoed message");
message->addPayload(echoPayload);
client->sendMessage(message);
}
return rosterController;
}
RosterController* Kaidan::getRosterController()
MessageController* Kaidan::getMessageController()
{
return rosterController_;
return messageController;
}
bool Kaidan::connectionState() const
......
/*
* Kaidan - Cross platform XMPP client
*
* Copyright (C) 2016 LNJ <git@lnj.li>
* Copyright (C) 2016-2017 LNJ <git@lnj.li>
* Copyright (C) 2016 Marzanna
* Copyright (C) 2016 geobra <s.g.b@gmx.de>
*
......@@ -33,12 +33,14 @@
#include "EchoPayloadParserFactory.h"
#include "EchoPayloadSerializer.h"
#include "RosterController.h"
#include "MessageController.h"
class Kaidan : public QObject
{
Q_OBJECT
Q_PROPERTY(RosterController* rosterController READ getRosterController NOTIFY rosterControllerChanged)
Q_PROPERTY(MessageController* messageController READ getMessageController NOTIFY messageControllerChanged)
Q_PROPERTY(bool connectionState READ connectionState NOTIFY connectionStateConnected NOTIFY connectionStateDisconnected)
Q_PROPERTY(QString jid READ getJid WRITE setJid NOTIFY jidChanged)
Q_PROPERTY(QString password READ getPassword WRITE setPassword NOTIFY passwordChanged)
......@@ -59,19 +61,20 @@ public:
void setPassword(QString);
RosterController* getRosterController();
MessageController* getMessageController();
signals:
void rosterControllerChanged();
void messageControllerChanged();
void connectionStateConnected();
void connectionStateDisconnected();
void jidChanged();
void passwordChanged();
void rosterControllerChanged();
private:
void handlePresenceReceived(Swift::Presence::ref presence);
void handleConnected();
void handleDisconnected();
void handleMessageReceived(Swift::Message::ref message);
bool connected;
Swift::Client* client;
......@@ -81,7 +84,8 @@ private:
EchoPayloadSerializer echoPayloadSerializer;
Swift::NetworkFactories *netFactories;
RosterController* rosterController_;
RosterController* rosterController;
MessageController* messageController;
QSettings* settings;
......
/*
* Kaidan - Cross platform XMPP client
*
* Copyright (C) 2017 LNJ <git@lnj.li>
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MessageController.h"
// Qt
#include <QDateTime>
#include <QDebug>
#include <QString>
#include <QSqlError>
#include <QSqlRecord>
#include <QSqlQuery>
// Boost
#include <boost/bind.hpp>
#include <boost/smart_ptr/make_shared.hpp>
// Swiften
#include <Swiften/Swiften.h>
// Kaidan
#include "EchoPayload.h"
#include "MessageModel.h"
MessageController::MessageController(Swift::Client* client_, QObject *parent) : QObject(parent)
{
client = client_;
client->onMessageReceived.connect(boost::bind(&MessageController::handleMessageReceived, this, _1));
messageModel = new MessageModel();
emit messageModelChanged();
}
MessageController::~MessageController()
{
}
MessageModel* MessageController::getMessageModel()
{
return messageModel;
}
void MessageController::setRecipient(QString recipient_)
{
if (recipient == recipient_)
return;
recipient = recipient_;
emit recipientChanged();
messageModel->applyRecipientFilter(recipient);
emit messageModelChanged();
}
QString MessageController::getRecipient()
{
return recipient;
}
void MessageController::handleMessageReceived(Swift::Message::ref message_)
{
// check if the message has a body -> is valid
if (!message_->getBody())
{
std::cout << "MessageController: Received message without body." << '\n';
return;
}
else
{
std::cout << "MessageController: Message received." << '\n';
}
//
// add the message to the db
//
// author is only the 'bare' JID: e.g. 'albert@einstein.ch'
const QString author = QString(message_->getFrom().toBare().toString().c_str());
const QString author_resource = QString(message_->getFrom().getResource().c_str());
const QString recipient = QString("Me");
const QString recipient_resource = QString("");
const QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
const QString message = QString(message_->getBody()->c_str());
messageModel->addMessage(&author, &author_resource, &recipient,
&recipient_resource, &timestamp, &message);
emit messageModelChanged();
}
void MessageController::sendMessage(const QString recipient_, const QString message_)
{
//
// add the message to the db
//
const QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
const QString author = QString("Me");
const QString author_resource = QString(client->getJID().getResource().c_str());
const QString recipient_resource = QString("");
messageModel->addMessage(&author, &author_resource, &recipient_,
&recipient_resource, &timestamp, &message_);
emit messageModelChanged();
//
// send the message
//
boost::shared_ptr<Swift::Message> newMessage(new Swift::Message());
newMessage->setTo(Swift::JID(recipient_.toStdString()));
newMessage->setFrom(client->getJID());
newMessage->setBody(message_.toStdString());
// send the message
client->sendMessage(newMessage);
std::cout << "MessageController: Sent new message from " << newMessage->getFrom() << " to " << newMessage->getTo() << '\n';
}
/*
* Kaidan - Cross platform XMPP client
*
* Copyright (C) 2016 geobra <s.g.b@gmx.de>
* Copyright (C) 2017 LNJ <git@lnj.li>
*
* Kaidan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -17,52 +17,44 @@
* along with Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ROSTERITEM_H
#define ROSTERITEM_H
#ifndef MESSAGECONTROLLER_H
#define MESSAGECONTROLLER_H
// Qt
#include <QObject>
#include <QSqlTableModel>
// Swiften
#include <Swiften/Swiften.h>
// Kaidan
#include "MessageModel.h"
enum Subscription
{
None,
To,
From,
Both,
Remove
};
class RosterItem : public QObject
class MessageController : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString jid READ getJid WRITE setJid NOTIFY jidChanged)
Q_PROPERTY(Subscription subscription READ getSubscription WRITE setSubscription NOTIFY subscriptionChanged)
Q_PROPERTY(MessageModel* messageModel READ getMessageModel NOTIFY messageModelChanged)
Q_PROPERTY(QString recipient READ getRecipient WRITE setRecipient NOTIFY recipientChanged)
public:
explicit RosterItem(QObject *parent = 0);
RosterItem(const QString& jid, const QString& name, const Subscription& subscription, QObject* parent = 0);
MessageController(Swift::Client* client_, QObject *parent = 0);
~MessageController();
QString getName();
void setName(const QString& name);
MessageModel* getMessageModel();
QString getJid();
void setJid(const QString& jid);
void setRecipient(QString recipient_);
QString getRecipient();
Subscription getSubscription();
void setSubscription(const Subscription& subscription);
Q_INVOKABLE void sendMessage(const QString recipient_, const QString message_);
signals:
void nameChanged();
void jidChanged();
void subscriptionChanged();
public slots:
void messageModelChanged();
void recipientChanged();
private:
QString jid_;
QString name_;
Subscription subscription_;
void handleMessageReceived(Swift::Message::ref message);
Swift::Client* client;
MessageModel* messageModel;
QString recipient;
};
#endif // ROSTERITEM_H
#endif // MESSAGECONTROLLER_H
/*
* Kaidan - Cross platform XMPP client
*
* Copyright (C) 2017 LNJ <git@lnj.li>
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MessageModel.h"
#include <QDateTime>
#include <QDebug>
#include <QSqlError>
#include <QSqlRecord>
#include <QSqlQuery>
static const char *conversationsTableName = "Messages";
static void createTable()
{
if (QSqlDatabase::database().tables().contains(conversationsTableName)) {
// The table already exists; we don't need to do anything.
return;
}
QSqlQuery query;
if (!query.exec(
"CREATE TABLE IF NOT EXISTS 'Messages' ("
"'author' TEXT NOT NULL,"
"'author_resource' TEXT,"
"'recipient' TEXT NOT NULL,"
"'recipient_resource' TEXT,"
"'timestamp' TEXT NOT NULL,"
"'message' TEXT NOT NULL,"
"FOREIGN KEY('author') REFERENCES Contacts ('jid'),"
"FOREIGN KEY('recipient') REFERENCES Contacts ('jid')"
")"))
{
qFatal("Failed to query database: %s", qPrintable(query.lastError().text()));
}
}
MessageModel::MessageModel(QObject *parent) :
QSqlTableModel(parent)
{
createTable();
setTable(conversationsTableName);
// sort in descending order of the timestamp column
setSort(4, Qt::DescendingOrder);
// Ensures that the model is sorted correctly after submitting a new row.
setEditStrategy(QSqlTableModel::OnManualSubmit);
}
void MessageModel::applyRecipientFilter(QString recipient_)
{
const QString filterString = QString::fromLatin1("(recipient = '%1' AND "
"author = 'Me') OR (recipient = 'Me' AND author='%1')").arg(recipient_);
setFilter(filterString);
select();
}
QVariant MessageModel::data(const QModelIndex &index, int role) const
{
if (role < Qt::UserRole)
return QSqlTableModel::data(index, role);
const QSqlRecord sqlRecord = record(index.row());
return sqlRecord.value(role - Qt::UserRole);
}
QHash<int, QByteArray> MessageModel::roleNames() const
{
QHash<int, QByteArray> names;
names[Qt::UserRole] = "author";
names[Qt::UserRole + 1] = "author_resource";
names[Qt::UserRole + 2] = "recipient";
names[Qt::UserRole + 3] = "recipient_resource";
names[Qt::UserRole + 4] = "timestamp";
names[Qt::UserRole + 5] = "message";
return names;
}
void MessageModel::addMessage(const QString* author, const QString* author_resource,
const QString* recipient, const QString* recipient_resource,
const QString* timestamp, const QString* message)
{
QSqlRecord newRecord = record();
newRecord.setValue("author", *author);
newRecord.setValue("author_resource", *author_resource);
newRecord.setValue("recipient", *recipient);
newRecord.setValue("recipient_resource", *recipient_resource);
newRecord.setValue("timestamp", *timestamp);
newRecord.setValue("message", *message);
if (!insertRecord(rowCount(), newRecord)) {
qWarning() << "Failed to add message to DB:" << lastError().text();
return;
}
submitAll();
}
/*
* Kaidan - Cross platform XMPP client
*
* Copyright (C) 2016 geobra <s.g.b@gmx.de>
* Copyright (C) 2017 LNJ <git@lnj.li>
*
* Kaidan is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -17,49 +17,30 @@
* along with Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#include "RosterItem.h"
#ifndef MESSAGEMODEL_H
#define MESSAGEMODEL_H
RosterItem::RosterItem(QObject *parent) : QObject(parent), subscription_(None), jid_(""), name_("")
{
}
RosterItem::RosterItem(const QString &jid, const QString &name, const Subscription &subscription, QObject* parent) :
QObject(parent), jid_(jid), name_(name), subscription_(subscription)
{
}
QString RosterItem::getName()
{
return name_;
}
#include <QSqlTableModel>
void RosterItem::setName(const QString &name)
class MessageModel : public QSqlTableModel
{
name_ = name;
Q_OBJECT
emit nameChanged();
}
public:
MessageModel(QObject *parent = 0);
QString RosterItem::getJid()
{
return jid_;
}
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
void RosterItem::setJid(const QString &jid)
{
jid_ = jid;
void applyRecipientFilter(QString recipient_);
void addMessage(const QString* author, const QString* author_resource,
const QString* recipient, const QString* recipient_resource,
const QString* timestamp, const QString* message);
emit jidChanged();
}
signals:
void recipientChanged();
Subscription RosterItem::getSubscription()
{
return subscription_;
}
void RosterItem::setSubscription(const Subscription &subscription)
{
subscription_ = subscription;
private:
};
emit subscriptionChanged();
}
#endif // MESSAGEMODEL_H
/*
* Kaidan - Cross platform XMPP client
*
* Copyright (C) 2017 LNJ <git@lnj.li>
* Copyright (C) 2016 geobra <s.g.b@gmx.de>
*
* Kaidan is free software: you can redistribute it and/or modify
......@@ -20,56 +21,92 @@
#include "RosterController.h"
#include <QQmlContext>
#include <QDebug>
#include <QSqlError>
#include <QSqlRecord>
#include <QSqlQuery>
RosterController::RosterController(QObject *parent) : QObject(parent), client_(NULL), rosterList_()
#include "RosterModel.h"
RosterController::RosterController(QObject *parent) : QObject(parent)
{
//rosterList_.append(new RosterItem(QString("lala@lala.de"), QString("lala"), None));
rosterModel = new RosterModel();
}
void RosterController::requestRosterFromClient(Swift::Client *client)
RosterController::~RosterController()
{
client_ = client;
client_->requestRoster();
}
RosterModel* RosterController::getRosterModel()
{
return rosterModel;
}
void RosterController::requestRosterFromClient(Swift::Client *client_)
{
client = client_;
client->requestRoster();
Swift::GetRosterRequest::ref rosterRequest = Swift::GetRosterRequest::create(client->getIQRouter());
rosterRequest->onResponse.connect(bind(&RosterController::handleRosterReceived, this, _2));
rosterRequest->send();
std::cout << "RosterController: Sent roster request\n";
}
void RosterController::handleRosterReceived(Swift::ErrorPayload::ref error)
void RosterController::handleRosterReceived(Swift::ErrorPayload::ref error_)
{
if (error)