1 /***************************************************************************
2 * Copyright (C) 2005-2012 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "coreconnection.h"
23 #ifndef QT_NO_NETWORKPROXY
24 # include <QNetworkProxy>
28 #include "clientsettings.h"
29 #include "coreaccountmodel.h"
32 #include "networkmodel.h"
34 #include "signalproxy.h"
37 CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent)
42 _wantReconnect(false),
47 _requestedDisconnect(false)
49 qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
53 void CoreConnection::init()
55 Client::signalProxy()->setHeartBeatInterval(30);
56 connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
57 connect(Client::signalProxy(), SIGNAL(lagUpdated(int)), SIGNAL(lagUpdated(int)));
59 _reconnectTimer.setSingleShot(true);
60 connect(&_reconnectTimer, SIGNAL(timeout()), SLOT(reconnectTimeout()));
63 connect(Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)),
64 SLOT(solidNetworkStatusChanged(Solid::Networking::Status)));
67 CoreConnectionSettings s;
68 s.initAndNotify("PingTimeoutInterval", this, SLOT(pingTimeoutIntervalChanged(QVariant)), 60);
69 s.initAndNotify("ReconnectInterval", this, SLOT(reconnectIntervalChanged(QVariant)), 60);
70 s.notify("NetworkDetectionMode", this, SLOT(networkDetectionModeChanged(QVariant)));
71 networkDetectionModeChanged(s.networkDetectionMode());
75 void CoreConnection::setProgressText(const QString &text)
77 if (_progressText != text) {
79 emit progressTextChanged(text);
84 void CoreConnection::setProgressValue(int value)
86 if (_progressValue != value) {
87 _progressValue = value;
88 emit progressValueChanged(value);
93 void CoreConnection::setProgressMinimum(int minimum)
95 if (_progressMinimum != minimum) {
96 _progressMinimum = minimum;
97 emit progressRangeChanged(minimum, _progressMaximum);
102 void CoreConnection::setProgressMaximum(int maximum)
104 if (_progressMaximum != maximum) {
105 _progressMaximum = maximum;
106 emit progressRangeChanged(_progressMinimum, maximum);
111 void CoreConnection::updateProgress(int value, int max)
113 if (max != _progressMaximum) {
114 _progressMaximum = max;
115 emit progressRangeChanged(_progressMinimum, _progressMaximum);
117 setProgressValue(value);
121 void CoreConnection::reconnectTimeout()
124 CoreConnectionSettings s;
125 if (_wantReconnect && s.autoReconnect()) {
127 // If using Solid, we don't want to reconnect if we're offline
128 if (s.networkDetectionMode() == CoreConnectionSettings::UseSolid) {
129 if (Solid::Networking::status() != Solid::Networking::Connected
130 && Solid::Networking::status() != Solid::Networking::Unknown) {
134 #endif /* HAVE_KDE */
142 void CoreConnection::networkDetectionModeChanged(const QVariant &vmode)
144 CoreConnectionSettings s;
145 CoreConnectionSettings::NetworkDetectionMode mode = (CoreConnectionSettings::NetworkDetectionMode)vmode.toInt();
146 if (mode == CoreConnectionSettings::UsePingTimeout)
147 Client::signalProxy()->setMaxHeartBeatCount(s.pingTimeoutInterval() / 30);
149 Client::signalProxy()->setMaxHeartBeatCount(-1);
154 void CoreConnection::pingTimeoutIntervalChanged(const QVariant &interval)
156 CoreConnectionSettings s;
157 if (s.networkDetectionMode() == CoreConnectionSettings::UsePingTimeout)
158 Client::signalProxy()->setMaxHeartBeatCount(interval.toInt() / 30); // interval is 30 seconds
162 void CoreConnection::reconnectIntervalChanged(const QVariant &interval)
164 _reconnectTimer.setInterval(interval.toInt() * 1000);
170 void CoreConnection::solidNetworkStatusChanged(Solid::Networking::Status status)
172 CoreConnectionSettings s;
173 if (s.networkDetectionMode() != CoreConnectionSettings::UseSolid)
177 case Solid::Networking::Unknown:
178 case Solid::Networking::Connected:
179 //qDebug() << "Solid: Network status changed to connected or unknown";
180 if (state() == Disconnected) {
181 if (_wantReconnect && s.autoReconnect()) {
186 case Solid::Networking::Disconnecting:
187 case Solid::Networking::Unconnected:
188 if (state() != Disconnected && !isLocalConnection())
189 disconnectFromCore(tr("Network is down"), true);
199 bool CoreConnection::isEncrypted() const
204 QSslSocket *sock = qobject_cast<QSslSocket *>(_socket);
205 return isConnected() && sock && sock->isEncrypted();
210 bool CoreConnection::isLocalConnection() const
214 if (currentAccount().isInternal())
216 if (_socket->peerAddress().isInSubnet(QHostAddress::LocalHost, 0x00ffffff))
223 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState)
227 switch (socketState) {
228 case QAbstractSocket::UnconnectedState:
229 text = tr("Disconnected");
231 case QAbstractSocket::HostLookupState:
232 text = tr("Looking up %1...").arg(currentAccount().hostName());
234 case QAbstractSocket::ConnectingState:
235 text = tr("Connecting to %1...").arg(currentAccount().hostName());
237 case QAbstractSocket::ConnectedState:
238 text = tr("Connected to %1").arg(currentAccount().hostName());
240 case QAbstractSocket::ClosingState:
241 text = tr("Disconnecting from %1...").arg(currentAccount().hostName());
248 emit progressTextChanged(text);
250 setState(socketState);
254 void CoreConnection::setState(QAbstractSocket::SocketState socketState)
256 ConnectionState state;
258 switch (socketState) {
259 case QAbstractSocket::UnconnectedState:
260 state = Disconnected;
262 case QAbstractSocket::HostLookupState:
263 case QAbstractSocket::ConnectingState:
264 case QAbstractSocket::ConnectedState: // we'll set it to Connected in connectionReady()
268 state = Disconnected;
275 void CoreConnection::setState(ConnectionState state)
277 if (state != _state) {
279 emit stateChanged(state);
280 if (state == Disconnected)
286 void CoreConnection::coreSocketError(QAbstractSocket::SocketError)
288 qDebug() << "coreSocketError" << _socket << _socket->errorString();
289 disconnectFromCore(_socket->errorString(), true);
293 void CoreConnection::coreSocketDisconnected()
295 // qDebug() << Q_FUNC_INFO;
296 _wasReconnect = !_requestedDisconnect;
297 resetConnection(true);
298 // FIXME handle disconnects gracefully
302 void CoreConnection::coreHasData()
305 while (SignalProxy::readDataFromDevice(_socket, _blockSize, item)) {
306 QVariantMap msg = item.toMap();
307 if (!msg.contains("MsgType")) {
308 // This core is way too old and does not even speak our init protocol...
309 emit connectionErrorPopup(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
310 disconnectFromCore(QString(), false);
313 if (msg["MsgType"] == "ClientInitAck") {
316 else if (msg["MsgType"] == "ClientInitReject") {
317 emit connectionErrorPopup(msg["Error"].toString());
318 disconnectFromCore(QString(), false);
321 else if (msg["MsgType"] == "CoreSetupAck") {
322 emit coreSetupSuccess();
324 else if (msg["MsgType"] == "CoreSetupReject") {
325 emit coreSetupFailed(msg["Error"].toString());
327 else if (msg["MsgType"] == "ClientLoginReject") {
328 loginFailed(msg["Error"].toString());
330 else if (msg["MsgType"] == "ClientLoginAck") {
333 else if (msg["MsgType"] == "SessionInit") {
334 // that's it, let's hand over to the signal proxy
335 // if the socket is an orphan, the signalProxy adopts it.
336 // -> we don't need to care about it anymore
337 _socket->setParent(0);
338 Client::signalProxy()->addPeer(_socket);
340 sessionStateReceived(msg["SessionState"].toMap());
341 break; // this is definitively the last message we process here!
344 disconnectFromCore(tr("Invalid data received from core"), false);
348 if (_blockSize > 0) {
349 updateProgress(_socket->bytesAvailable(), _blockSize);
354 void CoreConnection::disconnectFromCore()
356 _requestedDisconnect = true;
357 disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
361 void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect)
364 _reconnectTimer.stop();
366 _wasReconnect = wantReconnect; // store if disconnect was requested
368 if (errorString.isEmpty())
369 emit connectionError(tr("Disconnected"));
371 emit connectionError(errorString);
373 Client::signalProxy()->removeAllPeers();
374 resetConnection(wantReconnect);
378 void CoreConnection::resetConnection(bool wantReconnect)
380 _wantReconnect = wantReconnect;
383 disconnect(_socket, 0, this, 0);
384 _socket->deleteLater();
387 _requestedDisconnect = false;
390 _coreMsgBuffer.clear();
395 setProgressMaximum(-1); // disable
396 setState(Disconnected);
399 emit connectionMsg(tr("Disconnected from core."));
400 emit encrypted(false);
402 // initiate if a reconnect if appropriate
403 CoreConnectionSettings s;
404 if (wantReconnect && s.autoReconnect()) {
405 _reconnectTimer.start();
410 void CoreConnection::reconnectToCore()
412 if (currentAccount().isValid())
413 connectToCore(currentAccount().accountId());
417 bool CoreConnection::connectToCore(AccountId accId)
422 CoreAccountSettings s;
424 // FIXME: Don't force connection to internal core in mono client
425 if (Quassel::runMode() == Quassel::Monolithic) {
426 _account = accountModel()->account(accountModel()->internalAccount());
427 Q_ASSERT(_account.isValid());
430 if (!accId.isValid()) {
431 // check our settings and figure out what to do
432 if (!s.autoConnectOnStartup())
434 if (s.autoConnectToFixedAccount())
435 accId = s.autoConnectAccount();
437 accId = s.lastAccount();
438 if (!accId.isValid())
441 _account = accountModel()->account(accId);
442 if (!_account.accountId().isValid()) {
445 if (Quassel::runMode() != Quassel::Monolithic) {
446 if (_account.isInternal())
451 s.setLastAccount(accId);
452 connectToCurrentAccount();
457 void CoreConnection::connectToCurrentAccount()
459 resetConnection(false);
461 if (currentAccount().isInternal()) {
462 if (Quassel::runMode() != Quassel::Monolithic) {
463 qWarning() << "Cannot connect to internal core in client-only mode!";
466 emit startInternalCore();
467 emit connectToInternalCore(Client::instance()->signalProxy());
471 CoreAccountSettings s;
475 QSslSocket *sock = new QSslSocket(Client::instance());
476 // make sure the warning is shown if we happen to connect without SSL support later
477 s.setAccountValue("ShowNoClientSslWarning", true);
479 if (_account.useSsl()) {
480 if (s.accountValue("ShowNoClientSslWarning", true).toBool()) {
481 bool accepted = false;
482 emit handleNoSslInClient(&accepted);
484 emit connectionError(tr("Unencrypted connection canceled"));
487 s.setAccountValue("ShowNoClientSslWarning", false);
490 QTcpSocket *sock = new QTcpSocket(Client::instance());
493 #ifndef QT_NO_NETWORKPROXY
494 if (_account.useProxy()) {
495 QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
496 sock->setProxy(proxy);
501 connect(sock, SIGNAL(readyRead()), SLOT(coreHasData()));
502 connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected()));
503 connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
504 connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError)));
505 connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
507 emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName()));
508 sock->connectToHost(_account.hostName(), _account.port());
512 void CoreConnection::coreSocketConnected()
514 // Phase One: Send client info and wait for core info
516 emit connectionMsg(tr("Synchronizing to core..."));
518 QVariantMap clientInit;
519 clientInit["MsgType"] = "ClientInit";
520 clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
521 clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
522 clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
523 clientInit["UseSsl"] = _account.useSsl();
524 #ifndef QT_NO_COMPRESS
525 clientInit["UseCompression"] = true;
527 clientInit["UseCompression"] = false;
530 SignalProxy::writeDataToDevice(_socket, clientInit);
534 void CoreConnection::clientInitAck(const QVariantMap &msg)
536 // Core has accepted our version info and sent its own. Let's see if we accept it as well...
537 uint ver = msg["ProtocolVersion"].toUInt();
538 if (ver < Quassel::buildInfo().clientNeedsProtocol) {
539 emit connectionErrorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
540 "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
541 disconnectFromCore(QString(), false);
545 Client::setCoreFeatures((Quassel::Features)msg["CoreFeatures"].toUInt());
547 #ifndef QT_NO_COMPRESS
548 if (msg["SupportsCompression"].toBool()) {
549 _socket->setProperty("UseCompression", true);
553 _coreMsgBuffer = msg;
556 CoreAccountSettings s;
557 if (currentAccount().useSsl()) {
558 if (msg["SupportSsl"].toBool()) {
559 // Make sure the warning is shown next time we don't have SSL in the core
560 s.setAccountValue("ShowNoCoreSslWarning", true);
562 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
564 connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
565 connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
566 sslSocket->startClientEncryption();
569 if (s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
570 bool accepted = false;
571 emit handleNoSslInCore(&accepted);
573 disconnectFromCore(tr("Unencrypted connection canceled"), false);
576 s.setAccountValue("ShowNoCoreSslWarning", false);
577 s.setAccountValue("SslCert", QString());
584 // if we use SSL we wait for the next step until every SSL warning has been cleared
591 void CoreConnection::sslSocketEncrypted()
593 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
596 if (!socket->sslErrors().count()) {
597 // Cert is valid, so we don't want to store it as known
598 // That way, a warning will appear in case it becomes invalid at some point
599 CoreAccountSettings s;
600 s.setAccountValue("SSLCert", QString());
603 emit encrypted(true);
608 void CoreConnection::sslErrors()
610 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
613 CoreAccountSettings s;
614 QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
616 if (knownDigest != socket->peerCertificate().digest()) {
617 bool accepted = false;
618 bool permanently = false;
619 emit handleSslErrors(socket, &accepted, &permanently);
622 disconnectFromCore(tr("Unencrypted connection canceled"), false);
627 s.setAccountValue("SslCert", socket->peerCertificate().digest());
629 s.setAccountValue("SslCert", QString());
632 socket->ignoreSslErrors();
636 #endif /* HAVE_SSL */
638 void CoreConnection::connectionReady()
641 emit connectionMsg(tr("Connected to %1").arg(currentAccount().accountName()));
643 if (!_coreMsgBuffer["Configured"].toBool()) {
645 emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
647 else if (_coreMsgBuffer["LoginEnabled"].toBool()) {
650 _coreMsgBuffer.clear();
654 void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember)
656 _account.setUser(user);
657 _account.setPassword(password);
658 _account.setStorePassword(remember);
663 void CoreConnection::loginToCore(const QString &prevError)
665 emit connectionMsg(tr("Logging in..."));
666 if (currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
668 emit userAuthenticationRequired(&_account, &valid, prevError); // *must* be a synchronous call
669 if (!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
670 disconnectFromCore(tr("Login canceled"), false);
675 QVariantMap clientLogin;
676 clientLogin["MsgType"] = "ClientLogin";
677 clientLogin["User"] = currentAccount().user();
678 clientLogin["Password"] = currentAccount().password();
679 SignalProxy::writeDataToDevice(_socket, clientLogin);
683 void CoreConnection::loginFailed(const QString &error)
689 void CoreConnection::loginSuccess()
691 updateProgress(0, 0);
693 // save current account data
694 _model->createOrUpdateAccount(currentAccount());
697 _reconnectTimer.stop();
699 setProgressText(tr("Receiving session state"));
700 setState(Synchronizing);
701 emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
705 void CoreConnection::sessionStateReceived(const QVariantMap &state)
707 updateProgress(100, 100);
709 // rest of communication happens through SignalProxy...
710 disconnect(_socket, SIGNAL(readyRead()), this, 0);
711 disconnect(_socket, SIGNAL(connected()), this, 0);
717 void CoreConnection::internalSessionStateReceived(const QVariant &packedState)
719 updateProgress(100, 100);
721 setState(Synchronizing);
722 syncToCore(packedState.toMap());
726 void CoreConnection::syncToCore(const QVariantMap &sessionState)
728 if (sessionState.contains("CoreFeatures"))
729 Client::setCoreFeatures((Quassel::Features)sessionState["CoreFeatures"].toUInt());
731 setProgressText(tr("Receiving network states"));
732 updateProgress(0, 100);
735 foreach(QVariant vid, sessionState["Identities"].toList()) {
736 Client::instance()->coreIdentityCreated(vid.value<Identity>());
740 // FIXME: get rid of this crap -- why?
741 QVariantList bufferinfos = sessionState["BufferInfos"].toList();
742 NetworkModel *networkModel = Client::networkModel();
743 Q_ASSERT(networkModel);
744 foreach(QVariant vinfo, bufferinfos)
745 networkModel->bufferUpdated(vinfo.value<BufferInfo>()); // create BufferItems
747 QVariantList networkids = sessionState["NetworkIds"].toList();
749 // prepare sync progress thingys...
750 // FIXME: Care about removal of networks
751 _numNetsToSync = networkids.count();
752 updateProgress(0, _numNetsToSync);
754 // create network objects
755 foreach(QVariant networkid, networkids) {
756 NetworkId netid = networkid.value<NetworkId>();
757 if (Client::network(netid))
759 Network *net = new Network(netid, Client::instance());
760 _netsToSync.insert(net);
761 connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
762 connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
763 Client::addNetwork(net);
769 // this is also called for destroyed networks!
770 void CoreConnection::networkInitDone()
772 QObject *net = sender();
774 disconnect(net, 0, this, 0);
775 _netsToSync.remove(net);
776 updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
781 void CoreConnection::checkSyncState()
783 if (_netsToSync.isEmpty() && state() >= Synchronizing) {
784 setState(Synchronized);
785 setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
786 setProgressMaximum(-1);
792 void CoreConnection::doCoreSetup(const QVariant &setupData)
795 setup["MsgType"] = "CoreSetupData";
796 setup["SetupData"] = setupData;
797 SignalProxy::writeDataToDevice(_socket, setup);