From: Manuel Nickschas Date: Thu, 19 Nov 2009 17:16:51 +0000 (+0100) Subject: Introduce (very) basic, incomplete version of CoreConnection X-Git-Tag: 0.6-beta1~167 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=11444d0219a994969c633f62543f7ef302f43300 Introduce (very) basic, incomplete version of CoreConnection This class encapsulates the client-side connection to the core and replaces both ClientSyncer and CoreConnectDlg. Having this neatly encapsulated will allow for a much easier way to do, say, reconnects or tracking the connection state. This is a first preliminary version that can only sync to a hardcoded account. --- diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index c009f5ba..31fa5d7a 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -26,6 +26,7 @@ set(SOURCES clientuserinputhandler.cpp coreaccount.cpp coreaccountmodel.cpp + coreconnection.cpp execwrapper.cpp irclistmodel.cpp messagefilter.cpp @@ -51,6 +52,7 @@ set(MOC_HDRS clientuserinputhandler.h clientsyncer.h coreaccountmodel.h + coreconnection.h execwrapper.h irclistmodel.h messagefilter.h diff --git a/src/client/coreconnection.cpp b/src/client/coreconnection.cpp new file mode 100644 index 00000000..b2c040eb --- /dev/null +++ b/src/client/coreconnection.cpp @@ -0,0 +1,448 @@ +/*************************************************************************** + * Copyright (C) 2009 by the Quassel Project * + * devel@quassel-irc.org * + * * + * 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) version 3. * + * * + * 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 program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "coreconnection.h" + +#ifndef QT_NO_NETWORKPROXY +# include +#endif + +#include "client.h" +#include "coreaccountmodel.h" +#include "identity.h" +#include "network.h" +#include "networkmodel.h" +#include "quassel.h" +#include "signalproxy.h" +#include "util.h" + +CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent) + : QObject(parent), + _model(model), + _blockSize(0), + _progressMinimum(0), + _progressMaximum(-1), + _progressValue(-1) +{ + qRegisterMetaType("CoreConnection::ConnectionState"); + +} + +void CoreConnection::start() { + connectToCore(1); +} + +void CoreConnection::init() { + connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected())); +} + +void CoreConnection::setProgressText(const QString &text) { + if(_progressText != text) { + _progressText = text; + emit progressTextChanged(text); + } +} + +void CoreConnection::setProgressValue(int value) { + if(_progressValue != value) { + _progressValue = value; + emit progressValueChanged(value); + } +} + +void CoreConnection::setProgressMinimum(int minimum) { + if(_progressMinimum != minimum) { + _progressMinimum = minimum; + emit progressRangeChanged(minimum, _progressMaximum); + } +} + +void CoreConnection::setProgressMaximum(int maximum) { + if(_progressMaximum != maximum) { + _progressMaximum = maximum; + emit progressRangeChanged(_progressMinimum, maximum); + } +} + +void CoreConnection::updateProgress(int value, int max) { + if(max != _progressMaximum) { + _progressMaximum = max; + emit progressRangeChanged(_progressMinimum, _progressMaximum); + } + setProgressValue(value); +} + +void CoreConnection::resetConnection() { + if(_socket) { + disconnect(_socket, 0, this, 0); + _socket->deleteLater(); + _socket = 0; + } + _blockSize = 0; + + _coreMsgBuffer.clear(); + + _netsToSync.clear(); + _numNetsToSync = 0; + + setProgressMaximum(-1); // disable + emit connectionMsg(tr("Disconnected from core.")); +} + +void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState) { + QString text; + + switch(socketState) { + case QAbstractSocket::UnconnectedState: + text = tr("Disconnected."); + break; + case QAbstractSocket::HostLookupState: + text = tr("Looking up %1...").arg(currentAccount().hostName()); + break; + case QAbstractSocket::ConnectingState: + text = tr("Connecting to %1...").arg(currentAccount().hostName()); + break; + case QAbstractSocket::ConnectedState: + text = tr("Connected to %1.").arg(currentAccount().hostName()); + break; + case QAbstractSocket::ClosingState: + text = tr("Disconnecting from %1...").arg(currentAccount().hostName()); + break; + default: + break; + } + + if(!text.isEmpty()) + emit progressTextChanged(text); + + setState(socketState); +} + +void CoreConnection::setState(QAbstractSocket::SocketState socketState) { + ConnectionState state; + + switch(socketState) { + case QAbstractSocket::UnconnectedState: + state = Disconnected; + break; + case QAbstractSocket::HostLookupState: + case QAbstractSocket::ConnectingState: + state = Connecting; + break; + case QAbstractSocket::ConnectedState: + state = Connected; + break; + default: + state = Disconnected; + } + + setState(state); +} + +void CoreConnection::setState(ConnectionState state) { + if(state != _state) { + _state = state; + emit stateChanged(state); + } +} + +void CoreConnection::setWarningsHandler(const char *slot) { + resetWarningsHandler(); + connect(this, SIGNAL(handleIgnoreWarnings(bool)), this, slot); +} + +void CoreConnection::resetWarningsHandler() { + disconnect(this, SIGNAL(handleIgnoreWarnings(bool)), this, 0); +} + +void CoreConnection::coreSocketError(QAbstractSocket::SocketError) { + qDebug() << "coreSocketError" << _socket << _socket->errorString(); + emit connectionError(_socket->errorString()); + resetConnection(); +} + +void CoreConnection::coreSocketDisconnected() { + setState(Disconnected); + emit disconnected(); + resetConnection(); + // FIXME handle disconnects gracefully +} + +void CoreConnection::coreHasData() { + QVariant item; + while(SignalProxy::readDataFromDevice(_socket, _blockSize, item)) { + QVariantMap msg = item.toMap(); + if(!msg.contains("MsgType")) { + // This core is way too old and does not even speak our init protocol... + emit connectionError(tr("The Quassel Core you try to connect to is too old! Please consider upgrading.")); + disconnectFromCore(); + return; + } + if(msg["MsgType"] == "ClientInitAck") { + clientInitAck(msg); + } else if(msg["MsgType"] == "ClientInitReject") { + emit connectionError(msg["Error"].toString()); + disconnectFromCore(); + return; + } else if(msg["MsgType"] == "CoreSetupAck") { + //emit coreSetupSuccess(); + } else if(msg["MsgType"] == "CoreSetupReject") { + //emit coreSetupFailed(msg["Error"].toString()); + } else if(msg["MsgType"] == "ClientLoginReject") { + loginFailed(msg["Error"].toString()); + } else if(msg["MsgType"] == "ClientLoginAck") { + loginSuccess(); + } else if(msg["MsgType"] == "SessionInit") { + // that's it, let's hand over to the signal proxy + // if the socket is an orphan, the signalProxy adopts it. + // -> we don't need to care about it anymore + _socket->setParent(0); + Client::signalProxy()->addPeer(_socket); + + sessionStateReceived(msg["SessionState"].toMap()); + break; // this is definitively the last message we process here! + } else { + emit connectionError(tr("Invalid data received from core!
Disconnecting.")); + disconnectFromCore(); + return; + } + } + if(_blockSize > 0) { + updateProgress(_socket->bytesAvailable(), _blockSize); + } +} + +void CoreConnection::disconnectFromCore() { + if(isConnected()) { + Client::signalProxy()->removeAllPeers(); + resetConnection(); + } +} + +void CoreConnection::reconnectToCore() { + +} + +void CoreConnection::connectToCore(AccountId accId) { + resetConnection(); + _account = accountModel()->account(accId); + + if(!_account.accountId().isValid()) { + emit connectionError(tr("Invalid core account, cannot connect!")); + return; + } + + Q_ASSERT(!_socket); +#ifdef HAVE_SSL + QSslSocket *sock = new QSslSocket(Client::instance()); +#else + if(_account.useSsl()) { + emit connectionError(tr("This client is built without SSL Support!
Disable the usage of SSL in the account settings.")); + return; + } + QTcpSocket *sock = new QTcpSocket(Client::instance()); +#endif + +#ifndef QT_NO_NETWORKPROXY + if(_account.useProxy()) { + QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword()); + sock->setProxy(proxy); + } +#endif + + _socket = sock; + connect(sock, SIGNAL(readyRead()), SLOT(coreHasData())); + connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected())); + connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected())); + connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError))); + connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState))); + + emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName())); + sock->connectToHost(_account.hostName(), _account.port()); +} + +void CoreConnection::coreSocketConnected() { + // Phase One: Send client info and wait for core info + + emit connectionMsg(tr("Synchronizing to core...")); + + QVariantMap clientInit; + clientInit["MsgType"] = "ClientInit"; + clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString; + clientInit["ClientDate"] = Quassel::buildInfo().buildDate; + clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion; + clientInit["UseSsl"] = _account.useSsl(); +#ifndef QT_NO_COMPRESS + clientInit["UseCompression"] = true; +#else + clientInit["UseCompression"] = false; +#endif + + SignalProxy::writeDataToDevice(_socket, clientInit); +} + +void CoreConnection::clientInitAck(const QVariantMap &msg) { + // Core has accepted our version info and sent its own. Let's see if we accept it as well... + uint ver = msg["ProtocolVersion"].toUInt(); + if(ver < Quassel::buildInfo().clientNeedsProtocol) { + emit connectionError(tr("The Quassel Core you are trying to connect to is too old!
" + "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol)); + disconnectFromCore(); + return; + } + +#ifndef QT_NO_COMPRESS + if(msg["SupportsCompression"].toBool()) { + _socket->setProperty("UseCompression", true); + } +#endif + + _coreMsgBuffer = msg; +#ifdef HAVE_SSL + if(currentAccount().useSsl()) { + if(msg["SupportSsl"].toBool()) { + QSslSocket *sslSocket = qobject_cast(_socket); + Q_ASSERT(sslSocket); + connect(sslSocket, SIGNAL(encrypted()), this, SLOT(sslSocketEncrypted())); + connect(sslSocket, SIGNAL(sslErrors(const QList &)), this, SLOT(sslErrors(const QList &))); + + sslSocket->startClientEncryption(); + } else { + emit connectionError(tr("The Quassel Core you are trying to connect to does not support SSL!
If you want to connect anyways, disable the usage of SSL in the account settings.")); + disconnectFromCore(); + } + return; + } +#endif + // if we use SSL we wait for the next step until every SSL warning has been cleared + connectionReady(); + +} + +void CoreConnection::connectionReady() { + if(!_coreMsgBuffer["Configured"].toBool()) { + // start wizard + emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList()); + } else if(_coreMsgBuffer["LoginEnabled"].toBool()) { + loginToCore(); + } + _coreMsgBuffer.clear(); + resetWarningsHandler(); +} + +void CoreConnection::loginToCore() { + emit connectionMsg(tr("Logging in...")); + if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) { + emit userAuthenticationRequired(&_account); // *must* be a synchronous call + if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) { + disconnectFromCore(); + return; + } + } + + QVariantMap clientLogin; + clientLogin["MsgType"] = "ClientLogin"; + clientLogin["User"] = currentAccount().user(); + clientLogin["Password"] = currentAccount().password(); + SignalProxy::writeDataToDevice(_socket, clientLogin); +} + +void CoreConnection::loginFailed(const QString &error) { + emit userAuthenticationRequired(&_account, error); // *must* be a synchronous call + if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) { + disconnectFromCore(); + return; + } + loginToCore(); +} + +void CoreConnection::loginSuccess() { + updateProgress(0, 0); + setProgressText(tr("Receiving session state")); + setState(Synchronizing); + emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName())); +} + +void CoreConnection::sessionStateReceived(const QVariantMap &state) { + updateProgress(100, 100); + + // rest of communication happens through SignalProxy... + disconnect(_socket, SIGNAL(readyRead()), this, 0); + disconnect(_socket, SIGNAL(connected()), this, 0); + + //Client::instance()->setConnectedToCore(currentAccount().accountId(), _socket); + syncToCore(state); +} + +void CoreConnection::syncToCore(const QVariantMap &sessionState) { + setProgressText(tr("Receiving network states")); + updateProgress(0, 100); + + // create identities + foreach(QVariant vid, sessionState["Identities"].toList()) { + Client::instance()->coreIdentityCreated(vid.value()); + } + + // create buffers + // FIXME: get rid of this crap -- why? + QVariantList bufferinfos = sessionState["BufferInfos"].toList(); + NetworkModel *networkModel = Client::networkModel(); + Q_ASSERT(networkModel); + foreach(QVariant vinfo, bufferinfos) + networkModel->bufferUpdated(vinfo.value()); // create BufferItems + + QVariantList networkids = sessionState["NetworkIds"].toList(); + + // prepare sync progress thingys... + // FIXME: Care about removal of networks + _numNetsToSync = networkids.count(); + updateProgress(0, _numNetsToSync); + + // create network objects + foreach(QVariant networkid, networkids) { + NetworkId netid = networkid.value(); + if(Client::network(netid)) + continue; + Network *net = new Network(netid, Client::instance()); + _netsToSync.insert(net); + connect(net, SIGNAL(initDone()), SLOT(networkInitDone())); + connect(net, SIGNAL(destroyed()), SLOT(networkInitDone())); + Client::addNetwork(net); + } + checkSyncState(); +} + +void CoreConnection::networkInitDone() { + Network *net = qobject_cast(sender()); + Q_ASSERT(net); + disconnect(net, 0, this, 0); + _netsToSync.remove(net); + updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync); + checkSyncState(); +} + +void CoreConnection::checkSyncState() { + if(_netsToSync.isEmpty()) { + setState(Synchronized); + setProgressText(QString()); + setProgressMaximum(-1); + emit synchronized(); + } +} diff --git a/src/client/coreconnection.h b/src/client/coreconnection.h new file mode 100644 index 00000000..67b9b142 --- /dev/null +++ b/src/client/coreconnection.h @@ -0,0 +1,161 @@ +/*************************************************************************** + * Copyright (C) 2009 by the Quassel Project * + * devel@quassel-irc.org * + * * + * 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) version 3. * + * * + * 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 program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef CORECONNECTION_H_ +#define CORECONNECTION_H_ + +// TODO: support system application proxy (new in Qt 4.6) + +#include "QPointer" + +#ifdef HAVE_SSL +# include +#else +# include +#endif + +#include "coreaccount.h" +#include "types.h" + +class CoreAccountModel; +class Network; + +class CoreConnection : public QObject { + Q_OBJECT + +public: + enum ConnectionState { + Disconnected, + Connecting, + Connected, + Synchronizing, + Synchronized + }; + + CoreConnection(CoreAccountModel *model, QObject *parent = 0); + + void init(); + void start(); + + inline ConnectionState state() const; + inline bool isConnected() const; + inline CoreAccount currentAccount() const; + + inline int progressMinimum() const; + inline int progressMaximum() const; + inline int progressValue() const; + inline QString progressText() const; + +public slots: + void connectToCore(AccountId); + void reconnectToCore(); + void disconnectFromCore(); + +// void useInternalCore(); + +signals: + void stateChanged(CoreConnection::ConnectionState); + void synchronized(); + + void connectionError(const QString &errorMsg); + void connectionWarnings(const QStringList &warnings); + void connectionMsg(const QString &msg); + void disconnected(); + + void progressRangeChanged(int minimum, int maximum); + void progressValueChanged(int value); + void progressTextChanged(const QString &); + + void startCoreSetup(const QVariantList &); + + // This signal MUST be handled synchronously! + void userAuthenticationRequired(CoreAccount *, const QString &errorMessage = QString()); + + void handleIgnoreWarnings(bool permanently); + +private slots: + void socketStateChanged(QAbstractSocket::SocketState); + void coreSocketError(QAbstractSocket::SocketError); + void coreHasData(); + void coreSocketConnected(); + void coreSocketDisconnected(); + + void clientInitAck(const QVariantMap &msg); + + // for sync progress + void networkInitDone(); + void checkSyncState(); + + void syncToCore(const QVariantMap &sessionState); + //void internalSessionStateReceived(const QVariant &packedState); + void sessionStateReceived(const QVariantMap &state); + + void setWarningsHandler(const char *slot); + void resetWarningsHandler(); + void resetConnection(); + void connectionReady(); + //void doCoreSetup(const QVariant &setupData); + + void loginToCore(); + void loginSuccess(); + void loginFailed(const QString &errorMessage); + + void updateProgress(int value, int maximum); + void setProgressText(const QString &text); + void setProgressValue(int value); + void setProgressMinimum(int minimum); + void setProgressMaximum(int maximum); + + void setState(QAbstractSocket::SocketState socketState); + void setState(ConnectionState state); + +private: + CoreAccountModel *_model; + CoreAccount _account; + QVariantMap _coreMsgBuffer; + + QPointer _socket; + quint32 _blockSize; + ConnectionState _state; + + QSet _netsToSync; + int _numNetsToSync; + int _progressMinimum, _progressMaximum, _progressValue; + QString _progressText; + + QString _coreInfoString(const QVariantMap &); + + inline CoreAccountModel *accountModel() const; +}; + +Q_DECLARE_METATYPE(CoreConnection::ConnectionState) + +// Inlines +int CoreConnection::progressMinimum() const { return _progressMinimum; } +int CoreConnection::progressMaximum() const { return _progressMaximum; } +int CoreConnection::progressValue() const { return _progressValue; } +QString CoreConnection::progressText() const { return _progressText; } + +CoreConnection::ConnectionState CoreConnection::state() const { return _state; } +bool CoreConnection::isConnected() const { return state() >= Connected; } +CoreAccount CoreConnection::currentAccount() const { return _account; } +CoreAccountModel *CoreConnection::accountModel() const { return _model; } + +#endif