X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fclient%2Fcoreconnection.cpp;h=460b3b70ddc81ae6413cba9bd2487abe8c8aa5b0;hp=b2c040ebd15ef3902128b144d7a505cbe4d89003;hb=86bd6b1ffb870e65af6d830a2ea16471c348ed5a;hpb=11444d0219a994969c633f62543f7ef302f43300 diff --git a/src/client/coreconnection.cpp b/src/client/coreconnection.cpp index b2c040eb..460b3b70 100644 --- a/src/client/coreconnection.cpp +++ b/src/client/coreconnection.cpp @@ -25,6 +25,7 @@ #endif #include "client.h" +#include "clientsettings.h" #include "coreaccountmodel.h" #include "identity.h" #include "network.h" @@ -37,20 +38,33 @@ CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent) : QObject(parent), _model(model), _blockSize(0), + _state(Disconnected), + _wantReconnect(false), _progressMinimum(0), _progressMaximum(-1), _progressValue(-1) { qRegisterMetaType("CoreConnection::ConnectionState"); - -} - -void CoreConnection::start() { - connectToCore(1); } void CoreConnection::init() { + Client::signalProxy()->setHeartBeatInterval(30); connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected())); + 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::setProgressText(const QString &text) { @@ -89,21 +103,93 @@ void CoreConnection::updateProgress(int value, int max) { setProgressValue(value); } -void CoreConnection::resetConnection() { - if(_socket) { - disconnect(_socket, 0, this, 0); - _socket->deleteLater(); - _socket = 0; +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(); + } } - _blockSize = 0; +} - _coreMsgBuffer.clear(); +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); + } +} - _netsToSync.clear(); - _numNetsToSync = 0; +void CoreConnection::pingTimeoutIntervalChanged(const QVariant &interval) { + CoreConnectionSettings s; + if(s.networkDetectionMode() == CoreConnectionSettings::UsePingTimeout) + Client::signalProxy()->setMaxHeartBeatCount(interval.toInt() / 30); // interval is 30 seconds +} - setProgressMaximum(-1); // disable - emit connectionMsg(tr("Disconnected from core.")); +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; +#else + QSslSocket *sock = qobject_cast(_socket); + return isConnected() && sock && sock->isEncrypted(); +#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) { @@ -111,7 +197,7 @@ void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState switch(socketState) { case QAbstractSocket::UnconnectedState: - text = tr("Disconnected."); + text = tr("Disconnected"); break; case QAbstractSocket::HostLookupState: text = tr("Looking up %1...").arg(currentAccount().hostName()); @@ -120,7 +206,7 @@ void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState text = tr("Connecting to %1...").arg(currentAccount().hostName()); break; case QAbstractSocket::ConnectedState: - text = tr("Connected to %1.").arg(currentAccount().hostName()); + text = tr("Connected to %1").arg(currentAccount().hostName()); break; case QAbstractSocket::ClosingState: text = tr("Disconnecting from %1...").arg(currentAccount().hostName()); @@ -144,11 +230,9 @@ void CoreConnection::setState(QAbstractSocket::SocketState socketState) { break; case QAbstractSocket::HostLookupState: case QAbstractSocket::ConnectingState: + case QAbstractSocket::ConnectedState: // we'll set it to Connected in connectionReady() state = Connecting; break; - case QAbstractSocket::ConnectedState: - state = Connected; - break; default: state = Disconnected; } @@ -160,28 +244,19 @@ void CoreConnection::setState(ConnectionState state) { if(state != _state) { _state = state; emit stateChanged(state); + if(state == Disconnected) + emit disconnected(); } } -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(); + disconnectFromCore(_socket->errorString(), true); } void CoreConnection::coreSocketDisconnected() { - setState(Disconnected); - emit disconnected(); - resetConnection(); + qDebug() << Q_FUNC_INFO; + resetConnection(true); // FIXME handle disconnects gracefully } @@ -191,20 +266,20 @@ void CoreConnection::coreHasData() { 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(); + 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 connectionError(msg["Error"].toString()); - disconnectFromCore(); + emit connectionErrorPopup(msg["Error"].toString()); + disconnectFromCore(QString(), false); return; } else if(msg["MsgType"] == "CoreSetupAck") { - //emit coreSetupSuccess(); + emit coreSetupSuccess(); } else if(msg["MsgType"] == "CoreSetupReject") { - //emit coreSetupFailed(msg["Error"].toString()); + emit coreSetupFailed(msg["Error"].toString()); } else if(msg["MsgType"] == "ClientLoginReject") { loginFailed(msg["Error"].toString()); } else if(msg["MsgType"] == "ClientLoginAck") { @@ -219,8 +294,7 @@ void CoreConnection::coreHasData() { 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(); + disconnectFromCore(tr("Invalid data received from core"), false); return; } } @@ -230,32 +304,124 @@ void CoreConnection::coreHasData() { } void CoreConnection::disconnectFromCore() { - if(isConnected()) { - Client::signalProxy()->removeAllPeers(); - resetConnection(); + 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(); + resetConnection(wantReconnect); +} + +void CoreConnection::resetConnection(bool wantReconnect) { + _wantReconnect = wantReconnect; + + if(_socket) { + disconnect(_socket, 0, this, 0); + _socket->deleteLater(); + _socket = 0; + } + _blockSize = 0; + + _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(); } } void CoreConnection::reconnectToCore() { + if(currentAccount().isValid()) + 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; } -void CoreConnection::connectToCore(AccountId accId) { - resetConnection(); - _account = accountModel()->account(accId); +void CoreConnection::connectToCurrentAccount() { + resetConnection(false); - if(!_account.accountId().isValid()) { - emit connectionError(tr("Invalid core account, cannot connect!")); + if(currentAccount().isInternal()) { + if(Quassel::runMode() != Quassel::Monolithic) { + qWarning() << "Cannot connect to internal core in client-only mode!"; + return; + } + emit startInternalCore(); + emit connectToInternalCore(Client::instance()->signalProxy()); return; } + CoreAccountSettings s; + 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); #else if(_account.useSsl()) { - emit connectionError(tr("This client is built without SSL Support!
Disable the usage of SSL in the account settings.")); - return; + 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()); #endif @@ -302,9 +468,9 @@ 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!
" + emit connectionErrorPopup(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(); + disconnectFromCore(QString(), false); return; } @@ -315,28 +481,88 @@ void CoreConnection::clientInitAck(const QVariantMap &msg) { #endif _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(_socket); Q_ASSERT(sslSocket); - connect(sslSocket, SIGNAL(encrypted()), this, SLOT(sslSocketEncrypted())); - connect(sslSocket, SIGNAL(sslErrors(const QList &)), this, SLOT(sslErrors(const QList &))); - + connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted())); + connect(sslSocket, SIGNAL(sslErrors(const QList &)), SLOT(sslErrors())); 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(); + 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(); } return; } #endif // 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(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(); } +void CoreConnection::sslErrors() { + QSslSocket *socket = qobject_cast(sender()); + Q_ASSERT(socket); + + CoreAccountSettings s; + QByteArray knownDigest = s.accountValue("SslCert").toByteArray(); + + if(knownDigest != socket->peerCertificate().digest()) { + bool accepted = false; + bool permanently = false; + emit handleSslErrors(socket, &accepted, &permanently); + + if(!accepted) { + disconnectFromCore(tr("Unencrypted connection canceled"), false); + return; + } + + 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())); + if(!_coreMsgBuffer["Configured"].toBool()) { // start wizard emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList()); @@ -344,15 +570,22 @@ void CoreConnection::connectionReady() { loginToCore(); } _coreMsgBuffer.clear(); - resetWarningsHandler(); } -void CoreConnection::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()) { - emit userAuthenticationRequired(&_account); // *must* be a synchronous call - if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) { - disconnectFromCore(); + 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; } } @@ -365,16 +598,18 @@ void CoreConnection::loginToCore() { } 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(); + loginToCore(error); } void CoreConnection::loginSuccess() { updateProgress(0, 0); + + // save current account data + _model->createOrUpdateAccount(currentAccount()); + _model->save(); + + _reconnectTimer.stop(); + setProgressText(tr("Receiving session state")); setState(Synchronizing); emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName())); @@ -387,10 +622,16 @@ void CoreConnection::sessionStateReceived(const QVariantMap &state) { disconnect(_socket, SIGNAL(readyRead()), this, 0); disconnect(_socket, SIGNAL(connected()), this, 0); - //Client::instance()->setConnectedToCore(currentAccount().accountId(), _socket); syncToCore(state); } +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); @@ -429,8 +670,9 @@ void CoreConnection::syncToCore(const QVariantMap &sessionState) { checkSyncState(); } +// this is also called for destroyed networks! void CoreConnection::networkInitDone() { - Network *net = qobject_cast(sender()); + QObject *net = sender(); Q_ASSERT(net); disconnect(net, 0, this, 0); _netsToSync.remove(net); @@ -439,10 +681,17 @@ void CoreConnection::networkInitDone() { } void CoreConnection::checkSyncState() { - if(_netsToSync.isEmpty()) { + if(_netsToSync.isEmpty() && state() >= Synchronizing) { setState(Synchronized); - setProgressText(QString()); + 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); +}