Introduce (very) basic, incomplete version of CoreConnection
authorManuel Nickschas <sputnick@quassel-irc.org>
Thu, 19 Nov 2009 17:16:51 +0000 (18:16 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Sat, 28 Nov 2009 23:39:41 +0000 (00:39 +0100)
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.

src/client/CMakeLists.txt
src/client/coreconnection.cpp [new file with mode: 0644]
src/client/coreconnection.h [new file with mode: 0644]

index c009f5b..31fa5d7 100644 (file)
@@ -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 (file)
index 0000000..b2c040e
--- /dev/null
@@ -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 <QNetworkProxy>
+#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<ConnectionState>("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("<b>Invalid data received from core!</b><br>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("<b>This client is built without SSL Support!</b><br />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("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
+        "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<QSslSocket *>(_socket);
+      Q_ASSERT(sslSocket);
+      connect(sslSocket, SIGNAL(encrypted()), this, SLOT(sslSocketEncrypted()));
+      connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
+
+      sslSocket->startClientEncryption();
+    } else {
+      emit connectionError(tr("<b>The Quassel Core you are trying to connect to does not support SSL!</b><br />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<Identity>());
+  }
+
+  // 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<BufferInfo>());  // 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<NetworkId>();
+    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<Network *>(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 (file)
index 0000000..67b9b14
--- /dev/null
@@ -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 <QSslSocket>
+#else
+#  include <QTcpSocket>
+#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<QIODevice> _socket;
+  quint32 _blockSize;
+  ConnectionState _state;
+
+  QSet<Network *> _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