Finally fix reconnection logic
[quassel.git] / src / client / coreconnection.cpp
index bf80149..7112b85 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2009 by the Quassel Project                             *
+ *   Copyright (C) 2005-2013 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
@@ -15,7 +15,7 @@
  *   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.             *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
 #include "coreconnection.h"
 #include "clientsettings.h"
 #include "coreaccountmodel.h"
 #include "identity.h"
+#include "internalpeer.h"
 #include "network.h"
 #include "networkmodel.h"
 #include "quassel.h"
 #include "signalproxy.h"
 #include "util.h"
 
+#include "protocols/legacy/legacypeer.h"
+
 CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent)
-  : QObject(parent),
-  _model(model),
-  _blockSize(0),
-  _state(Disconnected),
-  _progressMinimum(0),
-  _progressMaximum(-1),
-  _progressValue(-1)
+    : QObject(parent),
+    _model(model),
+    _state(Disconnected),
+    _wantReconnect(false),
+    _wasReconnect(false),
+    _progressMinimum(0),
+    _progressMaximum(-1),
+    _progressValue(-1),
+    _resetting(false)
 {
-  qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
+    qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
+}
+
+
+void CoreConnection::init()
+{
+    Client::signalProxy()->setHeartBeatInterval(30);
+    connect(Client::signalProxy(), SIGNAL(lagUpdated(int)), SIGNAL(lagUpdated(int)));
+
+    _reconnectTimer.setSingleShot(true);
+    connect(&_reconnectTimer, SIGNAL(timeout()), SLOT(reconnectTimeout()));
+
+#ifdef HAVE_KDE
+    connect(Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)),
+        SLOT(solidNetworkStatusChanged(Solid::Networking::Status)));
+#endif
 
+    CoreConnectionSettings s;
+    s.initAndNotify("PingTimeoutInterval", this, SLOT(pingTimeoutIntervalChanged(QVariant)), 60);
+    s.initAndNotify("ReconnectInterval", this, SLOT(reconnectIntervalChanged(QVariant)), 60);
+    s.notify("NetworkDetectionMode", this, SLOT(networkDetectionModeChanged(QVariant)));
+    networkDetectionModeChanged(s.networkDetectionMode());
 }
 
-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::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::setProgressValue(int value) {
-  if(_progressValue != value) {
-    _progressValue = value;
-    emit progressValueChanged(value);
-  }
+
+void CoreConnection::reconnectTimeout()
+{
+    if (!_peer) {
+        CoreConnectionSettings s;
+        if (_wantReconnect && s.autoReconnect()) {
+#ifdef HAVE_KDE
+            // If using Solid, we don't want to reconnect if we're offline
+            if (s.networkDetectionMode() == CoreConnectionSettings::UseSolid) {
+                if (Solid::Networking::status() != Solid::Networking::Connected
+                    && Solid::Networking::status() != Solid::Networking::Unknown) {
+                    return;
+                }
+            }
+#endif /* HAVE_KDE */
+
+            reconnectToCore();
+        }
+    }
 }
 
-void CoreConnection::setProgressMinimum(int minimum) {
-  if(_progressMinimum != minimum) {
-    _progressMinimum = minimum;
-    emit progressRangeChanged(minimum, _progressMaximum);
-  }
+
+void CoreConnection::networkDetectionModeChanged(const QVariant &vmode)
+{
+    CoreConnectionSettings s;
+    CoreConnectionSettings::NetworkDetectionMode mode = (CoreConnectionSettings::NetworkDetectionMode)vmode.toInt();
+    if (mode == CoreConnectionSettings::UsePingTimeout)
+        Client::signalProxy()->setMaxHeartBeatCount(s.pingTimeoutInterval() / 30);
+    else {
+        Client::signalProxy()->setMaxHeartBeatCount(-1);
+    }
 }
 
-void CoreConnection::setProgressMaximum(int maximum) {
-  if(_progressMaximum != maximum) {
-    _progressMaximum = maximum;
-    emit progressRangeChanged(_progressMinimum, maximum);
-  }
+
+void CoreConnection::pingTimeoutIntervalChanged(const QVariant &interval)
+{
+    CoreConnectionSettings s;
+    if (s.networkDetectionMode() == CoreConnectionSettings::UsePingTimeout)
+        Client::signalProxy()->setMaxHeartBeatCount(interval.toInt() / 30);  // interval is 30 seconds
 }
 
-void CoreConnection::updateProgress(int value, int max) {
-  if(max != _progressMaximum) {
-    _progressMaximum = max;
-    emit progressRangeChanged(_progressMinimum, _progressMaximum);
-  }
-  setProgressValue(value);
+
+void CoreConnection::reconnectIntervalChanged(const QVariant &interval)
+{
+    _reconnectTimer.setInterval(interval.toInt() * 1000);
 }
 
-void CoreConnection::resetConnection() {
-  if(_socket) {
-    disconnect(_socket, 0, this, 0);
-    _socket->deleteLater();
-    _socket = 0;
-  }
-  _blockSize = 0;
 
-  _coreMsgBuffer.clear();
+#ifdef HAVE_KDE
 
-  _netsToSync.clear();
-  _numNetsToSync = 0;
+void CoreConnection::solidNetworkStatusChanged(Solid::Networking::Status status)
+{
+    CoreConnectionSettings s;
+    if (s.networkDetectionMode() != CoreConnectionSettings::UseSolid)
+        return;
 
-  setProgressMaximum(-1); // disable
-  setState(Disconnected);
-  emit connectionMsg(tr("Disconnected from core."));
-  emit encrypted(false);
+    switch (status) {
+    case Solid::Networking::Unknown:
+    case Solid::Networking::Connected:
+        //qDebug() << "Solid: Network status changed to connected or unknown";
+        if (state() == Disconnected) {
+            if (_wantReconnect && s.autoReconnect()) {
+                reconnectToCore();
+            }
+        }
+        break;
+    case Solid::Networking::Disconnecting:
+    case Solid::Networking::Unconnected:
+        if (state() != Disconnected && !isLocalConnection())
+            disconnectFromCore(tr("Network is down"), true);
+        break;
+    default:
+        break;
+    }
 }
 
-bool CoreConnection::isEncrypted() const {
-#ifndef HAVE_SSL
-  return false;
-#else
-  QSslSocket *sock = qobject_cast<QSslSocket *>(_socket);
-  return isConnected() && sock && sock->isEncrypted();
+
 #endif
+
+bool CoreConnection::isEncrypted() const
+{
+    return _peer && _peer->isSecure();
 }
 
-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:
-  case QAbstractSocket::ConnectedState:  // we'll set it to Connected in connectionReady()
-    state = Connecting;
-    break;
-  default:
-    state = Disconnected;
-  }
-
-  setState(state);
-}
-
-void CoreConnection::setState(ConnectionState state) {
-  if(state != _state) {
-    _state = state;
-    emit stateChanged(state);
-    if(state == Disconnected)
-      emit disconnected();
-  }
-}
-
-void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
-  qDebug() << "coreSocketError" << _socket << _socket->errorString();
-  emit connectionError(_socket->errorString());
-  resetConnection();
-}
-
-void CoreConnection::coreSocketDisconnected() {
-  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 connectionErrorPopup(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 connectionErrorPopup(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 {
-      disconnectFromCore(tr("Invalid data received from core"));
-      return;
-    }
-  }
-  if(_blockSize > 0) {
-    updateProgress(_socket->bytesAvailable(), _blockSize);
-  }
-}
-
-void CoreConnection::disconnectFromCore(const QString &errorString) {
-  if(errorString.isEmpty())
-    emit connectionError(tr("Disconnected"));
-  else
-    emit connectionError(errorString);
-
-  Client::signalProxy()->removeAllPeers();
-  resetConnection();
-}
-
-void CoreConnection::reconnectToCore() {
-  if(currentAccount().isValid())
-    connectToCore(currentAccount().accountId());
-}
-
-bool CoreConnection::connectToCore(AccountId accId) {
-  if(isConnected())
+
+bool CoreConnection::isLocalConnection() const
+{
+    if (!isConnected())
+        return false;
+    if (currentAccount().isInternal())
+        return true;
+    if (_peer->isLocal())
+        return true;
+
     return false;
+}
 
-  CoreAccountSettings s;
 
-  // FIXME: Don't force connection to internal core in mono client
-  if(Quassel::runMode() == Quassel::Monolithic) {
-    _account = accountModel()->account(accountModel()->internalAccount());
-    Q_ASSERT(_account.isValid());
-  } else {
-    if(!accId.isValid()) {
-      // check our settings and figure out what to do
-      if(!s.autoConnectOnStartup())
-        return false;
-      if(s.autoConnectToFixedAccount())
-        accId = s.autoConnectAccount();
-      else
-        accId = s.lastAccount();
-      if(!accId.isValid())
-        return false;
+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:
+    case QAbstractSocket::ConnectedState: // we'll set it to Connected in connectionReady()
+        state = Connecting;
+        break;
+    default:
+        state = Disconnected;
+    }
+
+    setState(state);
+}
+
+
+void CoreConnection::setState(ConnectionState state)
+{
+    if (state != _state) {
+        _state = state;
+        emit stateChanged(state);
+        if (state == Disconnected)
+            emit disconnected();
+    }
+}
+
+
+void CoreConnection::coreSocketError(QAbstractSocket::SocketError)
+{
+    disconnectFromCore(_socket->errorString(), true);
+}
+
+
+void CoreConnection::coreSocketDisconnected()
+{
+    _wasReconnect = false;
+    resetConnection(_wantReconnect);
+    // FIXME handle disconnects gracefully
+}
+
+
+// note: this still expects the legacy protocol
+// noteĀ²: after cleaning this up, we can probably get rid of _socket altogether
+void CoreConnection::coreHasData(const QVariant &item)
+{
+    QVariantMap msg = item.toMap();
+    if (!msg.contains("MsgType")) {
+        // This core is way too old and does not even speak our init protocol...
+        emit connectionErrorPopup(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
+        disconnectFromCore(QString(), false);
+        return;
+    }
+    if (msg["MsgType"] == "ClientInitAck") {
+        clientInitAck(msg);
+    }
+    else if (msg["MsgType"] == "ClientInitReject") {
+        emit connectionErrorPopup(msg["Error"].toString());
+        disconnectFromCore(QString(), false);
+        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 connection is an orphan, the signalProxy adopts it.
+        // -> we don't need to care about it anymore
+
+        disconnect(_peer, 0, this, 0);
+
+        _peer->setParent(0);
+        Client::signalProxy()->addPeer(_peer);
+
+        sessionStateReceived(msg["SessionState"].toMap());
+    }
+    else {
+        disconnectFromCore(tr("Invalid data received from core"), false);
+        return;
+    }
+}
+
+
+void CoreConnection::disconnectFromCore()
+{
+    if (_socket)
+        disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
+}
+
+
+void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect)
+{
+    if (wantReconnect)
+        _reconnectTimer.start();
+    else
+        _reconnectTimer.stop();
+
+    _wantReconnect = wantReconnect; // store if disconnect was requested
+    _wasReconnect = false;
+
+    if (_socket)
+        _socket->close();
+
+    if (errorString.isEmpty())
+        emit connectionError(tr("Disconnected"));
+    else
+        emit connectionError(errorString);
+}
+
+
+void CoreConnection::resetConnection(bool wantReconnect)
+{
+    if (_resetting)
+        return;
+    _resetting = true;
+
+    _wantReconnect = wantReconnect;
+
+    if (_peer) {
+        disconnect(_socket, 0, this, 0);
+        disconnect(_peer, 0, this, 0);
+        _peer->close();
+
+        if (_peer->parent() == this)
+            _peer->deleteLater(); // if it's not us, it belongs to the sigproxy which will delete it
+        _socket = 0;      // socket is owned and will be deleted by RemoteConnection
+        _peer = 0;
     }
-    _account = accountModel()->account(accId);
-    if(!_account.accountId().isValid()) {
-      return false;
+    else if (_socket) {
+        disconnect(_socket, 0, this, 0);
+        _socket->deleteLater();
+        _socket = 0;
     }
-    if(Quassel::runMode() != Quassel::Monolithic) {
-      if(_account.isInternal())
+
+    _coreMsgBuffer.clear();
+    _netsToSync.clear();
+    _numNetsToSync = 0;
+
+    setProgressMaximum(-1); // disable
+    setState(Disconnected);
+    emit lagUpdated(-1);
+
+    emit connectionMsg(tr("Disconnected from core."));
+    emit encrypted(false);
+
+    // initiate if a reconnect if appropriate
+    CoreConnectionSettings s;
+    if (wantReconnect && s.autoReconnect()) {
+        _reconnectTimer.start();
+    }
+
+    _resetting = false;
+}
+
+
+void CoreConnection::reconnectToCore()
+{
+    if (currentAccount().isValid()) {
+        _wasReconnect = true;
+        connectToCore(currentAccount().accountId());
+    }
+}
+
+
+bool CoreConnection::connectToCore(AccountId accId)
+{
+    if (isConnected())
         return false;
+
+    CoreAccountSettings s;
+
+    // FIXME: Don't force connection to internal core in mono client
+    if (Quassel::runMode() == Quassel::Monolithic) {
+        _account = accountModel()->account(accountModel()->internalAccount());
+        Q_ASSERT(_account.isValid());
+    }
+    else {
+        if (!accId.isValid()) {
+            // check our settings and figure out what to do
+            if (!s.autoConnectOnStartup())
+                return false;
+            if (s.autoConnectToFixedAccount())
+                accId = s.autoConnectAccount();
+            else
+                accId = s.lastAccount();
+            if (!accId.isValid())
+                return false;
+        }
+        _account = accountModel()->account(accId);
+        if (!_account.accountId().isValid()) {
+            return false;
+        }
+        if (Quassel::runMode() != Quassel::Monolithic) {
+            if (_account.isInternal())
+                return false;
+        }
     }
-  }
 
-  s.setLastAccount(accId);
-  connectToCurrentAccount();
-  return true;
+    s.setLastAccount(accId);
+    connectToCurrentAccount();
+    return true;
 }
 
-void CoreConnection::connectToCurrentAccount() {
-  resetConnection();
 
-  if(currentAccount().isInternal()) {
-    if(Quassel::runMode() != Quassel::Monolithic) {
-      qWarning() << "Cannot connect to internal core in client-only mode!";
-      return;
+void CoreConnection::connectToCurrentAccount()
+{
+    resetConnection(false);
+
+    if (currentAccount().isInternal()) {
+        if (Quassel::runMode() != Quassel::Monolithic) {
+            qWarning() << "Cannot connect to internal core in client-only mode!";
+            return;
+        }
+        emit startInternalCore();
+
+        InternalPeer *peer = new InternalPeer();
+        Client::instance()->signalProxy()->addPeer(peer); // sigproxy will take ownership
+        emit connectToInternalCore(peer);
+
+        return;
     }
-    emit startInternalCore();
-    emit connectToInternalCore(Client::instance()->signalProxy());
-    return;
-  }
 
-  CoreAccountSettings s;
+    CoreAccountSettings s;
 
-  Q_ASSERT(!_socket);
+    Q_ASSERT(!_socket);
 #ifdef HAVE_SSL
-  QSslSocket *sock = new QSslSocket(Client::instance());
-  // make sure the warning is shown if we happen to connect without SSL support later
-  s.setAccountValue("ShowNoClientSslWarning", true);
+    QSslSocket *sock = new QSslSocket(this);
+    // make sure the warning is shown if we happen to connect without SSL support later
+    s.setAccountValue("ShowNoClientSslWarning", true);
 #else
-  if(_account.useSsl()) {
-    if(s.accountValue("ShowNoClientSslWarning", true).toBool()) {
-      bool accepted = false;
-      emit handleNoSslInClient(&accepted);
-      if(!accepted) {
-        emit connectionError(tr("Unencrypted connection canceled"));
-        return;
-      }
-      s.setAccountValue("ShowNoClientSslWarning", false);
+    if (_account.useSsl()) {
+        if (s.accountValue("ShowNoClientSslWarning", true).toBool()) {
+            bool accepted = false;
+            emit handleNoSslInClient(&accepted);
+            if (!accepted) {
+                emit connectionError(tr("Unencrypted connection canceled"));
+                return;
+            }
+            s.setAccountValue("ShowNoClientSslWarning", false);
+        }
     }
-  }
-  QTcpSocket *sock = new QTcpSocket(Client::instance());
+    QTcpSocket *sock = new QTcpSocket(this);
 #endif
 
 #ifndef QT_NO_NETWORKPROXY
-  if(_account.useProxy()) {
-    QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
-    sock->setProxy(proxy);
-  }
+    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)));
+    _socket = sock;
+    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());
+    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();
+void CoreConnection::coreSocketConnected()
+{
+    // Create the connection which will handle the incoming data
+    Q_ASSERT(!_peer);
+    _peer = new LegacyPeer(_socket, this);
+    connect(_peer, SIGNAL(dataReceived(QVariant)), SLOT(coreHasData(QVariant)));
+    connect(_peer, SIGNAL(transferProgress(int,int)), SLOT(updateProgress(int,int)));
+
+    // 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;
+    clientInit["UseCompression"] = true;
 #else
-  clientInit["UseCompression"] = false;
+    clientInit["UseCompression"] = false;
 #endif
 
-  SignalProxy::writeDataToDevice(_socket, clientInit);
+    qobject_cast<RemotePeer *>(_peer)->writeSocketData(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 connectionErrorPopup(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;
-  }
+
+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 connectionErrorPopup(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(QString(), false);
+        return;
+    }
+
+    Client::setCoreFeatures((Quassel::Features)msg["CoreFeatures"].toUInt());
 
 #ifndef QT_NO_COMPRESS
-  if(msg["SupportsCompression"].toBool()) {
-    _socket->setProperty("UseCompression", true);
-  }
+    if (msg["SupportsCompression"].toBool()) {
+        _socket->setProperty("UseCompression", true);
+    }
 #endif
 
-  _coreMsgBuffer = msg;
+    _coreMsgBuffer = msg;
 
 #ifdef HAVE_SSL
-  CoreAccountSettings s;
-  if(currentAccount().useSsl()) {
-    if(msg["SupportSsl"].toBool()) {
-      // Make sure the warning is shown next time we don't have SSL in the core
-      s.setAccountValue("ShowNoCoreSslWarning", true);
-
-      QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
-      Q_ASSERT(sslSocket);
-      connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
-      connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
-      sslSocket->startClientEncryption();
-    } else {
-      if(s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
-        bool accepted = false;
-        emit handleNoSslInCore(&accepted);
-        if(!accepted) {
-          disconnectFromCore(tr("Unencrypted connection canceled"));
-          return;
+    CoreAccountSettings s;
+    if (currentAccount().useSsl()) {
+        if (msg["SupportSsl"].toBool()) {
+            // Make sure the warning is shown next time we don't have SSL in the core
+            s.setAccountValue("ShowNoCoreSslWarning", true);
+
+            QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
+            Q_ASSERT(sslSocket);
+            connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
+            connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
+            sslSocket->startClientEncryption();
+        }
+        else {
+            if (s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
+                bool accepted = false;
+                emit handleNoSslInCore(&accepted);
+                if (!accepted) {
+                    disconnectFromCore(tr("Unencrypted connection canceled"), false);
+                    return;
+                }
+                s.setAccountValue("ShowNoCoreSslWarning", false);
+                s.setAccountValue("SslCert", QString());
+            }
+            connectionReady();
         }
-        s.setAccountValue("ShowNoCoreSslWarning", false);
-        s.setAccountValue("SslCert", QString());
-      }
-      connectionReady();
+        return;
     }
-    return;
-  }
 #endif
-  // if we use SSL we wait for the next step until every SSL warning has been cleared
-  connectionReady();
+    // if we use SSL we wait for the next step until every SSL warning has been cleared
+    connectionReady();
 }
 
-#ifdef HAVE_SSL
 
-void CoreConnection::sslSocketEncrypted() {
-  QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
-  Q_ASSERT(socket);
+#ifdef HAVE_SSL
 
-  if(!socket->sslErrors().count()) {
-    // Cert is valid, so we don't want to store it as known
-    // That way, a warning will appear in case it becomes invalid at some point
-    CoreAccountSettings s;
-    s.setAccountValue("SSLCert", QString());
-  }
+void CoreConnection::sslSocketEncrypted()
+{
+    QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
+    Q_ASSERT(socket);
+
+    if (!socket->sslErrors().count()) {
+        // Cert is valid, so we don't want to store it as known
+        // That way, a warning will appear in case it becomes invalid at some point
+        CoreAccountSettings s;
+        s.setAccountValue("SSLCert", QString());
+    }
 
-  emit encrypted(true);
-  connectionReady();
+    emit encrypted(true);
+    connectionReady();
 }
 
-void CoreConnection::sslErrors() {
-  QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
-  Q_ASSERT(socket);
 
-  CoreAccountSettings s;
-  QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
+void CoreConnection::sslErrors()
+{
+    QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
+    Q_ASSERT(socket);
 
-  if(knownDigest != socket->peerCertificate().digest()) {
-    bool accepted = false;
-    bool permanently = false;
-    emit handleSslErrors(socket, &accepted, &permanently);
+    CoreAccountSettings s;
+    QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
 
-    if(!accepted) {
-      disconnectFromCore(tr("Unencrypted connection canceled"));
-      return;
-    }
+    if (knownDigest != socket->peerCertificate().digest()) {
+        bool accepted = false;
+        bool permanently = false;
+        emit handleSslErrors(socket, &accepted, &permanently);
 
-    if(permanently)
-      s.setAccountValue("SslCert", socket->peerCertificate().digest());
-    else
-      s.setAccountValue("SslCert", QString());
-  }
+        if (!accepted) {
+            disconnectFromCore(tr("Unencrypted connection canceled"), false);
+            return;
+        }
 
-  socket->ignoreSslErrors();
+        if (permanently)
+            s.setAccountValue("SslCert", socket->peerCertificate().digest());
+        else
+            s.setAccountValue("SslCert", QString());
+    }
+
+    socket->ignoreSslErrors();
 }
 
+
 #endif /* HAVE_SSL */
 
-void CoreConnection::connectionReady() {
-  setState(Connected);
-  emit connectionMsg(tr("Connected to %1").arg(currentAccount().accountName()));
+void CoreConnection::connectionReady()
+{
+    setState(Connected);
+    emit connectionMsg(tr("Connected to %1").arg(currentAccount().accountName()));
 
-  if(!_coreMsgBuffer["Configured"].toBool()) {
-    // start wizard
-    emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
-  } else if(_coreMsgBuffer["LoginEnabled"].toBool()) {
-    loginToCore();
-  }
-  _coreMsgBuffer.clear();
+    if (!_coreMsgBuffer["Configured"].toBool()) {
+        // start wizard
+        emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
+    }
+    else if (_coreMsgBuffer["LoginEnabled"].toBool()) {
+        loginToCore();
+    }
+    _coreMsgBuffer.clear();
 }
 
-void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember) {
-  _account.setUser(user);
-  _account.setPassword(password);
-  _account.setStorePassword(remember);
-  loginToCore();
+
+void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember)
+{
+    _account.setUser(user);
+    _account.setPassword(password);
+    _account.setStorePassword(remember);
+    loginToCore();
 }
 
-void CoreConnection::loginToCore(const QString &prevError) {
-  emit connectionMsg(tr("Logging in..."));
-  if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
-    bool valid = false;
-    emit userAuthenticationRequired(&_account, &valid, prevError);  // *must* be a synchronous call
-    if(!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
-      disconnectFromCore(tr("Login canceled"));
-      return;
+
+void CoreConnection::loginToCore(const QString &prevError)
+{
+    emit connectionMsg(tr("Logging in..."));
+    if (currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
+        bool valid = false;
+        emit userAuthenticationRequired(&_account, &valid, prevError); // *must* be a synchronous call
+        if (!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
+            disconnectFromCore(tr("Login canceled"), false);
+            return;
+        }
     }
-  }
 
-  QVariantMap clientLogin;
-  clientLogin["MsgType"] = "ClientLogin";
-  clientLogin["User"] = currentAccount().user();
-  clientLogin["Password"] = currentAccount().password();
-  SignalProxy::writeDataToDevice(_socket, clientLogin);
+    QVariantMap clientLogin;
+    clientLogin["MsgType"] = "ClientLogin";
+    clientLogin["User"] = currentAccount().user();
+    clientLogin["Password"] = currentAccount().password();
+    qobject_cast<RemotePeer*>(_peer)->writeSocketData(clientLogin);
 }
 
-void CoreConnection::loginFailed(const QString &error) {
-  loginToCore(error);
+
+void CoreConnection::loginFailed(const QString &error)
+{
+    loginToCore(error);
 }
 
-void CoreConnection::loginSuccess() {
-  updateProgress(0, 0);
 
-  // save current account data
-  _model->createOrUpdateAccount(currentAccount());
-  _model->save();
+void CoreConnection::loginSuccess()
+{
+    updateProgress(0, 0);
+
+    // save current account data
+    _model->createOrUpdateAccount(currentAccount());
+    _model->save();
 
-  setProgressText(tr("Receiving session state"));
-  setState(Synchronizing);
-  emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
+    _reconnectTimer.stop();
+
+    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);
+void CoreConnection::sessionStateReceived(const QVariantMap &state)
+{
+    updateProgress(100, 100);
 
-  syncToCore(state);
+    syncToCore(state);
 }
 
-void CoreConnection::internalSessionStateReceived(const QVariant &packedState) {
-  updateProgress(100, 100);
 
-  setState(Synchronizing);
-  syncToCore(packedState.toMap());
+void CoreConnection::internalSessionStateReceived(const QVariant &packedState)
+{
+    updateProgress(100, 100);
+
+    setState(Synchronizing);
+    syncToCore(packedState.toMap());
 }
 
-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>());
-  }
+void CoreConnection::syncToCore(const QVariantMap &sessionState)
+{
+    if (sessionState.contains("CoreFeatures"))
+        Client::setCoreFeatures((Quassel::Features)sessionState["CoreFeatures"].toUInt());
+
+    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)
+    // 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(tr("Synchronized to %1").arg(currentAccount().accountName()));
-    setProgressMaximum(-1);
-    emit synchronized();
-  }
-}
-
-void CoreConnection::doCoreSetup(const QVariant &setupData) {
-  QVariantMap setup;
-  setup["MsgType"] = "CoreSetupData";
-  setup["SetupData"] = setupData;
-  SignalProxy::writeDataToDevice(_socket, setup);
+    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();
+}
+
+
+// this is also called for destroyed networks!
+void CoreConnection::networkInitDone()
+{
+    QObject *net = sender();
+    Q_ASSERT(net);
+    disconnect(net, 0, this, 0);
+    _netsToSync.remove(net);
+    updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
+    checkSyncState();
+}
+
+
+void CoreConnection::checkSyncState()
+{
+    if (_netsToSync.isEmpty() && state() >= Synchronizing) {
+        setState(Synchronized);
+        setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
+        setProgressMaximum(-1);
+        emit synchronized();
+    }
+}
+
+
+void CoreConnection::doCoreSetup(const QVariant &setupData)
+{
+    QVariantMap setup;
+    setup["MsgType"] = "CoreSetupData";
+    setup["SetupData"] = setupData;
+    qobject_cast<RemotePeer *>(_peer)->writeSocketData(setup);
 }