Actually make the client/core connection settings do something
authorManuel Nickschas <sputnick@quassel-irc.org>
Mon, 7 Dec 2009 21:38:05 +0000 (22:38 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Mon, 7 Dec 2009 21:42:54 +0000 (22:42 +0100)
This introduces ping timeout settings and automatic reconnect. If you're using
KDE, you'll get Solid support to disconnect and reconnect as your network connection
status changes.

Thanks to sebas for the initial patch for Solid support!

Closes #405, closes #702

CMakeLists.txt
src/client/coreconnection.cpp
src/client/coreconnection.h

index 7df9079..f5e3632 100644 (file)
@@ -205,7 +205,7 @@ if(WITH_KDE)
     add_definitions(-DHAVE_KDE ${KDE4_DEFINITIONS})
     set(HAVE_KDE 1)
     set(MOC_DEFINES ${MOC_DEFINES} -DHAVE_KDE)
     add_definitions(-DHAVE_KDE ${KDE4_DEFINITIONS})
     set(HAVE_KDE 1)
     set(MOC_DEFINES ${MOC_DEFINES} -DHAVE_KDE)
-    set(QUASSEL_KDE_LIBRARIES ${KDE4_KDECORE_LIBS} ${KDE4_KDEUI_LIBRARY} knotifyconfig)
+    set(QUASSEL_KDE_LIBRARIES ${KDE4_KDECORE_LIBS} ${KDE4_KDEUI_LIBRARY} ${KDE4_SOLID_LIBS} knotifyconfig)
     # We always use external icons for KDE4 support, since we use its iconloader rather than our own
     set(EMBED_DATA OFF)
   else(KDE4_FOUND)
     # We always use external icons for KDE4 support, since we use its iconloader rather than our own
     set(EMBED_DATA OFF)
   else(KDE4_FOUND)
index bf80149..d20e434 100644 (file)
@@ -39,16 +39,31 @@ CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent)
   _model(model),
   _blockSize(0),
   _state(Disconnected),
   _model(model),
   _blockSize(0),
   _state(Disconnected),
+  _wantReconnect(false),
   _progressMinimum(0),
   _progressMaximum(-1),
   _progressValue(-1)
 {
   qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
 
   _progressMinimum(0),
   _progressMaximum(-1),
   _progressValue(-1)
 {
   qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
 
+  _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
 }
 
 void CoreConnection::init() {
 }
 
 void CoreConnection::init() {
+  Client::signalProxy()->setHeartBeatInterval(30);
   connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
   connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
+
+  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::setProgressText(const QString &text) {
 }
 
 void CoreConnection::setProgressText(const QString &text) {
@@ -87,7 +102,9 @@ void CoreConnection::updateProgress(int value, int max) {
   setProgressValue(value);
 }
 
   setProgressValue(value);
 }
 
-void CoreConnection::resetConnection() {
+void CoreConnection::resetConnection(bool wantReconnect) {
+  _wantReconnect = wantReconnect;
+
   if(_socket) {
     disconnect(_socket, 0, this, 0);
     _socket->deleteLater();
   if(_socket) {
     disconnect(_socket, 0, this, 0);
     _socket->deleteLater();
@@ -102,10 +119,87 @@ void CoreConnection::resetConnection() {
 
   setProgressMaximum(-1); // disable
   setState(Disconnected);
 
   setProgressMaximum(-1); // disable
   setState(Disconnected);
+
   emit connectionMsg(tr("Disconnected from core."));
   emit encrypted(false);
   emit connectionMsg(tr("Disconnected from core."));
   emit encrypted(false);
+
+  // initiate if a reconnect if appropriate
+  CoreConnectionSettings s;
+  if(wantReconnect && s.autoReconnect()) {
+    _reconnectTimer.start();
+    //reconnectToCore();
+  }
+}
+
+void CoreConnection::reconnectTimeout() {
+  if(!_socket) {
+    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::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::pingTimeoutIntervalChanged(const QVariant &interval) {
+  CoreConnectionSettings s;
+  if(s.networkDetectionMode() == CoreConnectionSettings::UsePingTimeout)
+    Client::signalProxy()->setMaxHeartBeatCount(interval.toInt() / 30); // interval is 30 seconds
+}
+
+void CoreConnection::reconnectIntervalChanged(const QVariant &interval) {
+  _reconnectTimer.setInterval(interval.toInt() * 1000);
 }
 
 }
 
+#ifdef HAVE_KDE
+
+void CoreConnection::solidNetworkStatusChanged(Solid::Networking::Status status) {
+  CoreConnectionSettings s;
+  if(s.networkDetectionMode() != CoreConnectionSettings::UseSolid)
+    return;
+
+  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::Unconnected:
+    qDebug() << "Solid: Disconnected";
+    if(!isLocalConnection())
+      disconnectFromCore(tr("Network is down"), true);
+    break;
+  default:
+    break;
+  }
+}
+
+#endif
+
 bool CoreConnection::isEncrypted() const {
 #ifndef HAVE_SSL
   return false;
 bool CoreConnection::isEncrypted() const {
 #ifndef HAVE_SSL
   return false;
@@ -115,6 +209,17 @@ bool CoreConnection::isEncrypted() const {
 #endif
 }
 
 #endif
 }
 
+bool CoreConnection::isLocalConnection() const {
+  if(!isConnected())
+    return false;
+  if(currentAccount().isInternal())
+    return true;
+  if(_socket->peerAddress().isInSubnet(QHostAddress::LocalHost, 0x00ffffff))
+    return true;
+
+  return false;
+}
+
 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
   QString text;
 
 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
   QString text;
 
@@ -174,13 +279,13 @@ void CoreConnection::setState(ConnectionState state) {
 
 void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
   qDebug() << "coreSocketError" << _socket << _socket->errorString();
 
 void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
   qDebug() << "coreSocketError" << _socket << _socket->errorString();
-  emit connectionError(_socket->errorString());
-  resetConnection();
+  disconnectFromCore(_socket->errorString(), true);
 }
 
 void CoreConnection::coreSocketDisconnected() {
   emit disconnected();
 }
 
 void CoreConnection::coreSocketDisconnected() {
   emit disconnected();
-  resetConnection();
+  qDebug() << Q_FUNC_INFO;
+  resetConnection(true);
   // FIXME handle disconnects gracefully
 }
 
   // FIXME handle disconnects gracefully
 }
 
@@ -191,14 +296,14 @@ void CoreConnection::coreHasData() {
     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."));
     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();
+      disconnectFromCore(QString(), false);
       return;
     }
     if(msg["MsgType"] == "ClientInitAck") {
       clientInitAck(msg);
     } else if(msg["MsgType"] == "ClientInitReject") {
       emit connectionErrorPopup(msg["Error"].toString());
       return;
     }
     if(msg["MsgType"] == "ClientInitAck") {
       clientInitAck(msg);
     } else if(msg["MsgType"] == "ClientInitReject") {
       emit connectionErrorPopup(msg["Error"].toString());
-      disconnectFromCore();
+      disconnectFromCore(QString(), false);
       return;
     } else if(msg["MsgType"] == "CoreSetupAck") {
       emit coreSetupSuccess();
       return;
     } else if(msg["MsgType"] == "CoreSetupAck") {
       emit coreSetupSuccess();
@@ -218,7 +323,7 @@ void CoreConnection::coreHasData() {
       sessionStateReceived(msg["SessionState"].toMap());
       break; // this is definitively the last message we process here!
     } else {
       sessionStateReceived(msg["SessionState"].toMap());
       break; // this is definitively the last message we process here!
     } else {
-      disconnectFromCore(tr("Invalid data received from core"));
+      disconnectFromCore(tr("Invalid data received from core"), false);
       return;
     }
   }
       return;
     }
   }
@@ -227,14 +332,21 @@ void CoreConnection::coreHasData() {
   }
 }
 
   }
 }
 
-void CoreConnection::disconnectFromCore(const QString &errorString) {
+void CoreConnection::disconnectFromCore() {
+  disconnectFromCore(QString(), false);  // requested disconnect, so don't try to reconnect
+}
+
+void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect) {
+  if(!wantReconnect)
+    _reconnectTimer.stop();
+
   if(errorString.isEmpty())
     emit connectionError(tr("Disconnected"));
   else
     emit connectionError(errorString);
 
   Client::signalProxy()->removeAllPeers();
   if(errorString.isEmpty())
     emit connectionError(tr("Disconnected"));
   else
     emit connectionError(errorString);
 
   Client::signalProxy()->removeAllPeers();
-  resetConnection();
+  resetConnection(wantReconnect);
 }
 
 void CoreConnection::reconnectToCore() {
 }
 
 void CoreConnection::reconnectToCore() {
@@ -280,7 +392,7 @@ bool CoreConnection::connectToCore(AccountId accId) {
 }
 
 void CoreConnection::connectToCurrentAccount() {
 }
 
 void CoreConnection::connectToCurrentAccount() {
-  resetConnection();
+  resetConnection(false);
 
   if(currentAccount().isInternal()) {
     if(Quassel::runMode() != Quassel::Monolithic) {
 
   if(currentAccount().isInternal()) {
     if(Quassel::runMode() != Quassel::Monolithic) {
@@ -358,7 +470,7 @@ void CoreConnection::clientInitAck(const QVariantMap &msg) {
   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));
   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();
+    disconnectFromCore(QString(), false);
     return;
   }
 
     return;
   }
 
@@ -387,7 +499,7 @@ void CoreConnection::clientInitAck(const QVariantMap &msg) {
         bool accepted = false;
         emit handleNoSslInCore(&accepted);
         if(!accepted) {
         bool accepted = false;
         emit handleNoSslInCore(&accepted);
         if(!accepted) {
-          disconnectFromCore(tr("Unencrypted connection canceled"));
+          disconnectFromCore(tr("Unencrypted connection canceled"), false);
           return;
         }
         s.setAccountValue("ShowNoCoreSslWarning", false);
           return;
         }
         s.setAccountValue("ShowNoCoreSslWarning", false);
@@ -432,7 +544,7 @@ void CoreConnection::sslErrors() {
     emit handleSslErrors(socket, &accepted, &permanently);
 
     if(!accepted) {
     emit handleSslErrors(socket, &accepted, &permanently);
 
     if(!accepted) {
-      disconnectFromCore(tr("Unencrypted connection canceled"));
+      disconnectFromCore(tr("Unencrypted connection canceled"), false);
       return;
     }
 
       return;
     }
 
@@ -473,7 +585,7 @@ void CoreConnection::loginToCore(const QString &prevError) {
     bool valid = false;
     emit userAuthenticationRequired(&_account, &valid, prevError);  // *must* be a synchronous call
     if(!valid || currentAccount().user().isEmpty() || currentAccount().password().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"));
+      disconnectFromCore(tr("Login canceled"), false);
       return;
     }
   }
       return;
     }
   }
@@ -496,6 +608,8 @@ void CoreConnection::loginSuccess() {
   _model->createOrUpdateAccount(currentAccount());
   _model->save();
 
   _model->createOrUpdateAccount(currentAccount());
   _model->save();
 
+  _reconnectTimer.stop();
+
   setProgressText(tr("Receiving session state"));
   setState(Synchronizing);
   emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
   setProgressText(tr("Receiving session state"));
   setState(Synchronizing);
   emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
index 2f9d7cc..6a3f37b 100644 (file)
@@ -24,6 +24,7 @@
 // TODO: support system application proxy (new in Qt 4.6)
 
 #include "QPointer"
 // TODO: support system application proxy (new in Qt 4.6)
 
 #include "QPointer"
+#include "QTimer"
 
 #ifdef HAVE_SSL
 #  include <QSslSocket>
 
 #ifdef HAVE_SSL
 #  include <QSslSocket>
 #  include <QTcpSocket>
 #endif
 
 #  include <QTcpSocket>
 #endif
 
+#ifdef HAVE_KDE
+#  include <Solid/Networking>
+#endif
+
 #include "coreaccount.h"
 #include "types.h"
 
 #include "coreaccount.h"
 #include "types.h"
 
@@ -54,11 +59,12 @@ public:
 
   void init();
 
 
   void init();
 
-  inline ConnectionState state() const;
   inline bool isConnected() const;
   inline bool isConnected() const;
+  inline ConnectionState state() const;
   inline CoreAccount currentAccount() const;
 
   bool isEncrypted() const;
   inline CoreAccount currentAccount() const;
 
   bool isEncrypted() const;
+  bool isLocalConnection() const;
 
   inline int progressMinimum() const;
   inline int progressMaximum() const;
 
   inline int progressMinimum() const;
   inline int progressMaximum() const;
@@ -72,7 +78,7 @@ public:
 public slots:
   bool connectToCore(AccountId = 0);
   void reconnectToCore();
 public slots:
   bool connectToCore(AccountId = 0);
   void reconnectToCore();
-  void disconnectFromCore(const QString &errorString = QString());
+  void disconnectFromCore();
 
 signals:
   void stateChanged(CoreConnection::ConnectionState);
 
 signals:
   void stateChanged(CoreConnection::ConnectionState);
@@ -106,6 +112,7 @@ signals:
 
 private slots:
   void connectToCurrentAccount();
 
 private slots:
   void connectToCurrentAccount();
+  void disconnectFromCore(const QString &errorString, bool wantReconnect = true);
 
   void socketStateChanged(QAbstractSocket::SocketState);
   void coreSocketError(QAbstractSocket::SocketError);
 
   void socketStateChanged(QAbstractSocket::SocketState);
   void coreSocketError(QAbstractSocket::SocketError);
@@ -123,7 +130,7 @@ private slots:
   void internalSessionStateReceived(const QVariant &packedState);
   void sessionStateReceived(const QVariantMap &state);
 
   void internalSessionStateReceived(const QVariant &packedState);
   void sessionStateReceived(const QVariantMap &state);
 
-  void resetConnection();
+  void resetConnection(bool wantReconnect = false);
   void connectionReady();
 
   void loginToCore(const QString &user, const QString &password, bool remember); // for config wizard
   void connectionReady();
 
   void loginToCore(const QString &user, const QString &password, bool remember); // for config wizard
@@ -147,15 +154,27 @@ private slots:
   void sslErrors();
 #endif
 
   void sslErrors();
 #endif
 
+  void networkDetectionModeChanged(const QVariant &mode);
+  void pingTimeoutIntervalChanged(const QVariant &interval);
+  void reconnectIntervalChanged(const QVariant &interval);
+  void reconnectTimeout();
+
+#ifdef HAVE_KDE
+  void solidNetworkStatusChanged(Solid::Networking::Status status);
+#endif
+
 private:
   CoreAccountModel *_model;
   CoreAccount _account;
   QVariantMap _coreMsgBuffer;
 
 private:
   CoreAccountModel *_model;
   CoreAccount _account;
   QVariantMap _coreMsgBuffer;
 
-  QPointer<QIODevice> _socket;
+  QPointer<QAbstractSocket> _socket;
   quint32 _blockSize;
   ConnectionState _state;
 
   quint32 _blockSize;
   ConnectionState _state;
 
+  QTimer _reconnectTimer;
+  bool _wantReconnect;
+
   QSet<Network *> _netsToSync;
   int _numNetsToSync;
   int _progressMinimum, _progressMaximum, _progressValue;
   QSet<Network *> _netsToSync;
   int _numNetsToSync;
   int _progressMinimum, _progressMaximum, _progressValue;