Commit 5c513623 authored by LNJ's avatar LNJ Committed by Jonah Brüchert

Use new ClientThread for XMPP-connection; Make offline usable

This rewrites the full back-end <-> front-end communication, moves the
connection logic to the back-end and makes the XMPP client connection
independant of the user interface (they will run parallel). However, the
database still runs on the main, GUI thread, so this will still block GUI
rendering (will be done in one of the next commits).

Now, Kaidan is able to handle different DisconnectionReason, so it can
distinguish if the authentication failed or there's just no connection to the
server available. So this will finally make Kaidan offline useable! But it
currently won't reconenct after losing the connection once.

The bug that Kaidan always crashed, when logging in after logging out has also
been fixed with this.

The LogInPage was simplified: now, the button only shows "Connecting..." (when
pressed) or "Connect". The trivial state of "retry" has been removed.

`branding.h` was renamed to `Globals.h`. The new `Enums` namespace is used to
register enums as a QMetaEnum, to make them printable and accessible in QML. The
new `Q_ENUM_NS` macro is used for that.

Coding style: I started to use doxygen-compatible inline documentation for the
most functions, classes, enums and other elements. I don't want to really use
doxygen (we're not a library), but it should definitely make it easier for new
developers to understand the code. So from now on new code should be documented
like this.

Closes #107 - Make Kaidan offline usable.
parent 476eccae
Subproject commit ed7df288eda31fb52287082a129fec9c58f5af52
Subproject commit 6800b88f18192e1646c41f8c78e832886f9f990a
Subproject commit 72f49c2026345579fb5444b930d6fa670fb849f7
Subproject commit 667cc4318a189cc21f8a98655cacd75add6c9f30
......@@ -8,6 +8,8 @@ CONFIG += c++11
SOURCES += \
src/main.cpp \
src/Kaidan.cpp \
src/ClientThread.cpp \
src/ClientWorker.cpp \
src/AvatarFileStorage.cpp \
src/Database.cpp \
src/RosterModel.cpp \
......@@ -37,8 +39,11 @@ HEADERS += \
src/PresenceHandler.h \
src/MessageHandler.h \
src/Kaidan.h \
src/ClientThread.h \
src/ClientWorker.h \
src/VCardManager.h \
src/branding.h \
src/Globals.h \
src/Enums.h \
src/StatusBar.h
android: INCLUDEPATH += $$PWD/3rdparty/gloox/include
......
......@@ -4,6 +4,8 @@ set(CURDIR ${CMAKE_CURRENT_LIST_DIR})
set(KAIDAN_SOURCES
${CURDIR}/main.cpp
${CURDIR}/Kaidan.cpp
${CURDIR}/ClientThread.cpp
${CURDIR}/ClientWorker.cpp
${CURDIR}/AvatarFileStorage.cpp
${CURDIR}/Database.cpp
${CURDIR}/RosterModel.cpp
......@@ -18,4 +20,7 @@ set(KAIDAN_SOURCES
${CURDIR}/VCardManager.cpp
${CURDIR}/XmlLogHandler.cpp
${CURDIR}/StatusBar.cpp
# needed to trigger moc generation
${CURDIR}/Enums.h
)
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your 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 "ClientThread.h"
// Kaidan
#include "ClientWorker.h"
#include "AvatarFileStorage.h"
#include "RosterManager.h"
#include "PresenceHandler.h"
#include "ServiceDiscoveryManager.h"
#include "Database.h"
#include "MessageSessionHandler.h"
#include "MessageHandler.h"
#include "VCardManager.h"
#include "XmlLogHandler.h"
// Qt
#include <QDebug>
#include <QMutexLocker>
#include <QTimer>
// gloox
#include <gloox/rostermanager.h>
#include <gloox/receipt.h>
#include <gloox/forward.h>
#include <gloox/carbons.h>
#include <gloox/vcardmanager.h>
#include <gloox/vcardupdate.h>
// package fetch interval in ms
static const unsigned int KAIDAN_CLIENT_LOOP_INTERVAL = 30;
ClientThread::ClientThread(RosterModel *rosterModel, MessageModel *messageModel,
AvatarFileStorage *avatarStorage, Credentials creds,
QSettings *settings, QObject *parent):
QThread(parent), rosterModel(rosterModel),
messageModel(messageModel), avatarStorage(avatarStorage),
creds(creds), settings(settings),
connState(ConnectionState::StateNone)
{
// Set custom thread name
setObjectName("XmppClient");
client = new gloox::Client(gloox::JID(creds.jid.toStdString()),
creds.password.toStdString());
client->bindResource(creds.jidResource.toStdString()); // set resource / device name
client->setTls(gloox::TLSRequired); // require encryption
worker = new ClientWorker(client, this);
connect(this, &ClientThread::connectRequested, worker, &ClientWorker::xmppConnect);
connect(this, &ClientThread::disconnectRequested, worker, &ClientWorker::xmppDisconnect);
connect(this, &ClientThread::stopWorkTimerRequested, worker, &ClientWorker::stopWorkTimer);
// The constructor is executed in the main thread, so we still need to move
// all QObject-based objects to the client thread.
worker->moveToThread(this);
workTimer.moveToThread(this);
}
ClientThread::~ClientThread()
{
// This is being executed in the main thread, not the client thread itself
// stop client loop timer from client thread
emit stopWorkTimerRequested();
exit(); // exit event loop
wait(); // wait for run() to finish
delete worker;
delete messageSessionHandler;
delete xmlLogHandler;
}
void ClientThread::run()
{
//
// Construct client and subclasses
//
// components
messageSessionHandler = new MessageSessionHandler(client, messageModel, rosterModel);
vCardManager = new VCardManager(client, avatarStorage, rosterModel);
rosterManager = new RosterManager(client, rosterModel, vCardManager);
presenceHandler = new PresenceHandler(client);
serviceDiscoveryManager = new ServiceDiscoveryManager(client, client->disco());
xmlLogHandler = new XmlLogHandler(client);
// Register Stanza Extensions
client->registerStanzaExtension(new gloox::Receipt(gloox::Receipt::Request));
client->registerStanzaExtension(new gloox::DelayedDelivery(gloox::JID(), std::string("")));
client->registerStanzaExtension(new gloox::Forward());
client->registerStanzaExtension(new gloox::Carbons());
client->registerStanzaExtension(new gloox::VCardUpdate());
// connect slots
connect(this, &ClientThread::sendMessageRequested,
messageSessionHandler->getMessageHandler(), &MessageHandler::sendMessage);
connect(this, &ClientThread::addContactRequested,
rosterManager, &RosterManager::addContact);
connect(this, &ClientThread::removeContactRequested,
rosterManager, &RosterManager::removeContact);
// timed fetching of packages
connect(&workTimer, &QTimer::timeout, worker, &ClientWorker::updateClient);
workTimer.start(KAIDAN_CLIENT_LOOP_INTERVAL);
// enter event loop
qDebug() << "[client] Entering main loop...";
exec();
}
void ClientThread::setCredentials(Credentials creds)
{
QMutexLocker locker(&mutex); // => locking mutex in this function
this->creds = creds;
client->setUsername(gloox::JID(creds.jid.toStdString()).username());
client->setServer(gloox::JID(creds.jid.toStdString()).server());
client->setPassword(creds.password.toStdString());
client->unbindResource(client->resource());
client->bindResource(creds.jidResource.toStdString());
}
void ClientThread::setCurrentChatPartner(QString *jid)
{
QMutexLocker locker(&mutex); // => locking mutex in this function
messageSessionHandler->getMessageHandler()->setCurrentChatPartner(jid);
}
void ClientThread::setConnectionState(ConnectionState state)
{
connState = state;
emit connectionStateChanged(state);
}
void ClientThread::setConnectionError(gloox::ConnectionError error)
{
connError = error;
emit disconnReasonChanged((DisconnReason) error);
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your 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/>.
*/
#ifndef CLIENTTHREAD_H
#define CLIENTTHREAD_H
// Qt
#include <QMutex>
#include <QThread>
#include <QTimer>
// Kaidan
#include "Enums.h"
namespace gloox {
class Client;
}
class ClientWorker;
class RosterManager;
class RosterModel;
class MessageSessionHandler;
class MessageModel;
class AvatarFileStorage;
class PresenceHandler;
class ServiceDiscoveryManager;
class VCardManager;
class XmlLogHandler;
class QSettings;
using namespace Enums;
/**
* A class controlling the thread used for the XMPP connection.
*
* @see ClientWorker
*/
class ClientThread : public QThread
{
Q_OBJECT
friend class ClientWorker; // can access private vars
public:
struct Credentials {
QString jid;
QString jidResource;
QString password;
// if never connected successfully before with these credentials
bool isFirstTry;
};
/**
* @param rosterModel The roster model connected to the database
* @param messageModel The message model connected to the database
* @param avatarStorage The storage class for caching user avatars
* @param creds The used credentials for connection
* @param settings Settings used for saving creds. on successful connect
* @param parent Optional QObject-based parent
*/
ClientThread(RosterModel *rosterModel, MessageModel *messageModel,
AvatarFileStorage *avatarStorage, Credentials creds,
QSettings *settings, QObject *parent = nullptr);
/*
* Will exit the event loop and waits until thread finishes and then
* destroys the object
*/
~ClientThread();
/**
* Sets the new credentials for next connect.
*
* @param jid The users's JID
* @param jidResource The resource/device name used for connecting
* @param password The user's password
*/
void setCredentials(Credentials creds);
/**
* Applys filters to the database for showing the correct chat.
*/
void setCurrentChatPartner(QString *jid);
/**
* Returns if connection state is connected.
*/
bool isConnected()
{
return connState == ConnectionState::StateConnected;
}
/**
* Returns the current connection state
*
* @see Enums::ConnectionState
*/
ConnectionState getConnectionState()
{
return connState;
}
/**
* Returns the last error that caused a disconnection.
*/
DisconnReason getConnectionError()
{
return (DisconnReason) connError;
}
protected:
/**
* The thread's main function which will initialize the client and it's
* subcomponents.
*/
void run() override;
signals:
/**
* Emitted when the connection state has changed.
*/
void connectionStateChanged(ConnectionState state);
/**
* Emitted when the client failed to connect giving the reason of it.
*/
void disconnReasonChanged(DisconnReason reason);
/**
* Emit to start connecting on the client's thread.
*/
void connectRequested();
/**
* Emit to start the disconnection on the client's thread.
*/
void disconnectRequested();
/**
* Emit to send a message to a chat partner.
*/
void sendMessageRequested(QString toJid, QString message);
/**
* Emit to add a new contact to the roster.
*/
void addContactRequested(QString jid, QString name);
/**
* Emit to remove a contact from the roster.
*/
void removeContactRequested(QString jid);
/**
* Emit to stop the work timer (needed because it has to be killed on
* its thread).
*/
void stopWorkTimerRequested();
/**
* Emit to get new credentials from QML
*/
void newCredentialsNeeded();
/**
* Emitted, when logging in with new credentials worked
*/
void logInWorked();
private:
/**
* Emits the signal for a given connection state.
*
* @param state The new connection state
*/
void setConnectionState(ConnectionState state);
/**
* Sets the new error as Enums::DisconnReason and emits the according
* signal
*
* @param error The last connection error
*/
void setConnectionError(gloox::ConnectionError error);
RosterModel *rosterModel;
MessageModel *messageModel;
AvatarFileStorage *avatarStorage;
Credentials creds;
gloox::Client *client;
ClientWorker *worker;
RosterManager *rosterManager;
MessageSessionHandler *messageSessionHandler;
PresenceHandler *presenceHandler;
ServiceDiscoveryManager *serviceDiscoveryManager;
VCardManager *vCardManager;
XmlLogHandler *xmlLogHandler;
QSettings *settings;
QMutex mutex;
QTimer workTimer;
ConnectionState connState = ConnectionState::StateNone;
gloox::ConnectionError connError = gloox::ConnNoError;
};
#endif // CLIENTTHREAD_H
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2017-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your 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 "ClientWorker.h"
// gloox
#include <gloox/client.h>
// Qt
#include <QDebug>
#include <QSettings>
// Kaidan
#include "ClientThread.h"
ClientWorker::ClientWorker(gloox::Client* client, ClientThread *controller,
QObject* parent) : QObject(parent), client(client), controller(controller)
{
client->registerConnectionListener(this);
}
void ClientWorker::updateClient()
{
if (controller->connState != ConnectionState::StateNone && controller->connState !=
ConnectionState::StateDisconnected) {
controller->mutex.lock();
client->recv(0);
controller->mutex.unlock();
}
}
void ClientWorker::xmppConnect()
{
qDebug() << "[client] Connecting...";
controller->setConnectionState(ConnectionState::StateConnecting);
controller->mutex.lock();
client->connect(false);
controller->mutex.unlock();
}
void ClientWorker::xmppDisconnect()
{
qDebug() << "[client] Disconnecting...";
if (controller->connState != ConnectionState::StateDisconnecting) {
controller->setConnectionState(ConnectionState::StateDisconnecting);
controller->mutex.lock();
client->disconnect();
controller->mutex.unlock();
}
}
void ClientWorker::onConnect()
{
// no mutex needed, because this is called from updateClient()
qDebug() << "[client] Connected successfully to server";
controller->setConnectionState(ConnectionState::StateConnected);
// Emit signal, that logging in with these credentials has worked for the first time
if (controller->creds.isFirstTry)
emit controller->logInWorked();
// accept credentials and save them
controller->creds.isFirstTry = false;
controller->settings->setValue("auth/jid", controller->creds.jid);
controller->settings->setValue("auth/password", controller->creds.password);
}
void ClientWorker::onDisconnect(gloox::ConnectionError error)
{
// no mutex needed, because this is called from updateClient()
qDebug() << "[client] Disconnected:" << (DisconnReason) error;
// update connection state and error
controller->setConnectionError(error);
controller->setConnectionState(ConnectionState::StateDisconnected);
// Check if first time connecting with these credentials
if (controller->creds.isFirstTry) {
// always request new credentials, when failed to connect on first time
emit controller->newCredentialsNeeded();
} else {
// already connected with these credentials once
if (error == gloox::ConnAuthenticationFailed)
// if JID/password is wrong, request new credentials from QML
emit controller->newCredentialsNeeded();
}
}
bool ClientWorker::onTLSConnect(const gloox::CertInfo &info)
{
// no mutex needed, because this is called from updateClient()
// accept certificate
qDebug() << "[client] Accepted TLS certificate without checking";
return true;
}
void ClientWorker::stopWorkTimer()
{
controller->workTimer.stop();
}
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2017-2018 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your 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/>.
*/
#ifndef CLIENTWORKER_H
#define CLIENTWORKER_H
// Qt
#include <QObject>
// gloox
#include <gloox/connectionlistener.h>
namespace gloox {
class Client;
}
class ClientThread;
/**
* The ClientWorker is used as a QObject-based worker on the ClientThread.
*
* @see ClientThread
*/
class ClientWorker : public QObject, public gloox::ConnectionListener
{
Q_OBJECT
public:
/**
* @param client XMPP Client which will be worked on.
* @param controller The ClientThread instance for emitting signals.
* @param parent Optional QObject-based parent.
*/
ClientWorker(gloox::Client *client, ClientThread *contoller,
QObject *parent = nullptr);
public slots:
/**
* Main function that will be executed via a QTimer.
*
* If the client is not disconnected and not untouched, this will fetch
* new packages from the client and process them.
*/
void updateClient();
/**
* Connects the client with the server.
*/
void xmppConnect();
/**
* Disconnects the client from server.
*/
void xmppDisconnect();
/**
* Destruct timer used for client loop (needs to be killed on the client
* thread)
*/
void stopWorkTimer();
private:
/**
* Notifys via signal that the client has connected.
*/
virtual void onConnect();
/**
* Saves error reason and emits a disconnect signal.
*/
virtual void onDisconnect(gloox::ConnectionError error);
/**
* Always accepts the TLS certificate
*
* @todo Automatically check if certificate is valid and ask user.
*/
virtual bool onTLSConnect(const gloox::CertInfo &info);
gloox::Client *client;
ClientThread *controller;
};
#endif // CLIENTWORKER_H
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2018 Kaidan developers and contributors (see the LICENSE
* file for a full list of copyright authors)
*
* 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.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL project's
* "OpenSSL" library (or with modified versions of it that use the same
* license as the "OpenSSL" library), and distribute the linked
* executables. You must obey the GNU General Public License in all
* respects for all of the code used other than "OpenSSL". If you modify
* this file, you may extend this exception to your version of the file,
* but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your 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
* this package. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ENUMS_H
#define ENUMS_H
#include <QtGlobal>
#include <QObject>
#include <gloox/gloox.h>
namespace Enums {
Q_NAMESPACE
/**
* Enumration of possible connection states.
*/
enum class ConnectionState : quint8 {
StateNone,
StateConnecting,
StateConnected,
StateDisconnecting,
StateDisconnected
};
Q_ENUM_NS(ConnectionState)
/**
* Enumration of possible disconnection reasons (compatible to gloox::
* ConnectionError)
*/
enum class DisconnectionReason : quint8 {
ConnNoError = gloox::ConnNoError,
ConnStreamError = gloox::ConnStreamError,
ConnStreamVersionError = gloox::ConnStreamVersionError,
ConnStreamClosed = gloox::ConnStreamClosed,
ConnProxyAuthRequired = gloox::ConnProxyAuthRequired,
ConnProxyAuthFailed = gloox::ConnProxyAuthFailed,
ConnProxyNoSupportedAuth = gloox::ConnProxyNoSupportedAuth,
ConnIoError = gloox::ConnIoError,
ConnParseError = gloox::ConnParseError,
ConnConnectionRefused = gloox::ConnConnectionRefused,
ConnDnsError = gloox::ConnDnsError,