#endif
#include "client.h"
+#include "clientsettings.h"
#include "coreaccountmodel.h"
#include "identity.h"
#include "network.h"
: QObject(parent),
_model(model),
_blockSize(0),
+ _state(Disconnected),
+ _wantReconnect(false),
_progressMinimum(0),
_progressMaximum(-1),
- _progressValue(-1)
+ _progressValue(-1),
+ _wasReconnect(false),
+ _requestedDisconnect(false)
{
qRegisterMetaType<ConnectionState>("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) {
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::Disconnecting:
+ case Solid::Networking::Unconnected:
+ if(state() != Disconnected && !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<QSslSocket *>(_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) {
switch(socketState) {
case QAbstractSocket::UnconnectedState:
- text = tr("Disconnected.");
+ text = tr("Disconnected");
break;
case QAbstractSocket::HostLookupState:
text = tr("Looking up %1...").arg(currentAccount().hostName());
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());
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;
}
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;
+ _wasReconnect = !_requestedDisconnect;
+ resetConnection(true);
// FIXME handle disconnects gracefully
}
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") {
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();
+ disconnectFromCore(tr("Invalid data received from core"), false);
return;
}
}
}
void CoreConnection::disconnectFromCore() {
- if(isConnected()) {
- Client::signalProxy()->removeAllPeers();
- resetConnection();
+ _requestedDisconnect = true;
+ disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
+}
+
+void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect) {
+ if(!wantReconnect)
+ _reconnectTimer.stop();
+
+ _wasReconnect = wantReconnect; // store if disconnect was requested
+
+ 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;
+ }
+ _requestedDisconnect = false;
+ _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("<b>This client is built without SSL Support!</b><br />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
// 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>"
+ 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;
}
+ Client::setCoreFeatures((Quassel::Features)msg["CoreFeatures"].toUInt());
+
#ifndef QT_NO_COMPRESS
if(msg["SupportsCompression"].toBool()) {
_socket->setProperty("UseCompression", true);
#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<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> &)));
-
+ connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
+ connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
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();
+ 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<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();
+}
+
+void CoreConnection::sslErrors() {
+ QSslSocket *socket = qobject_cast<QSslSocket *>(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());
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;
}
}
}
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()));
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) {
+ if(sessionState.contains("CoreFeatures"))
+ Client::setCoreFeatures((Quassel::Features)sessionState["CoreFeatures"].toUInt());
+
setProgressText(tr("Receiving network states"));
updateProgress(0, 100);
checkSyncState();
}
+// this is also called for destroyed networks!
void CoreConnection::networkInitDone() {
- Network *net = qobject_cast<Network *>(sender());
+ QObject *net = sender();
Q_ASSERT(net);
disconnect(net, 0, this, 0);
_netsToSync.remove(net);
}
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);
+}