1 /***************************************************************************
2 * Copyright (C) 2005-2013 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"
31 #include "internalpeer.h"
33 #include "networkmodel.h"
35 #include "signalproxy.h"
38 #include "protocols/legacy/legacypeer.h"
40 CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent)
44 _wantReconnect(false),
49 _requestedDisconnect(false),
52 qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
56 void CoreConnection::init()
58 Client::signalProxy()->setHeartBeatInterval(30);
59 connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
60 connect(Client::signalProxy(), SIGNAL(lagUpdated(int)), SIGNAL(lagUpdated(int)));
62 _reconnectTimer.setSingleShot(true);
63 connect(&_reconnectTimer, SIGNAL(timeout()), SLOT(reconnectTimeout()));
66 connect(Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)),
67 SLOT(solidNetworkStatusChanged(Solid::Networking::Status)));
70 CoreConnectionSettings s;
71 s.initAndNotify("PingTimeoutInterval", this, SLOT(pingTimeoutIntervalChanged(QVariant)), 60);
72 s.initAndNotify("ReconnectInterval", this, SLOT(reconnectIntervalChanged(QVariant)), 60);
73 s.notify("NetworkDetectionMode", this, SLOT(networkDetectionModeChanged(QVariant)));
74 networkDetectionModeChanged(s.networkDetectionMode());
78 void CoreConnection::setProgressText(const QString &text)
80 if (_progressText != text) {
82 emit progressTextChanged(text);
87 void CoreConnection::setProgressValue(int value)
89 if (_progressValue != value) {
90 _progressValue = value;
91 emit progressValueChanged(value);
96 void CoreConnection::setProgressMinimum(int minimum)
98 if (_progressMinimum != minimum) {
99 _progressMinimum = minimum;
100 emit progressRangeChanged(minimum, _progressMaximum);
105 void CoreConnection::setProgressMaximum(int maximum)
107 if (_progressMaximum != maximum) {
108 _progressMaximum = maximum;
109 emit progressRangeChanged(_progressMinimum, maximum);
114 void CoreConnection::updateProgress(int value, int max)
116 if (max != _progressMaximum) {
117 _progressMaximum = max;
118 emit progressRangeChanged(_progressMinimum, _progressMaximum);
120 setProgressValue(value);
124 void CoreConnection::reconnectTimeout()
127 CoreConnectionSettings s;
128 if (_wantReconnect && s.autoReconnect()) {
130 // If using Solid, we don't want to reconnect if we're offline
131 if (s.networkDetectionMode() == CoreConnectionSettings::UseSolid) {
132 if (Solid::Networking::status() != Solid::Networking::Connected
133 && Solid::Networking::status() != Solid::Networking::Unknown) {
137 #endif /* HAVE_KDE */
145 void CoreConnection::networkDetectionModeChanged(const QVariant &vmode)
147 CoreConnectionSettings s;
148 CoreConnectionSettings::NetworkDetectionMode mode = (CoreConnectionSettings::NetworkDetectionMode)vmode.toInt();
149 if (mode == CoreConnectionSettings::UsePingTimeout)
150 Client::signalProxy()->setMaxHeartBeatCount(s.pingTimeoutInterval() / 30);
152 Client::signalProxy()->setMaxHeartBeatCount(-1);
157 void CoreConnection::pingTimeoutIntervalChanged(const QVariant &interval)
159 CoreConnectionSettings s;
160 if (s.networkDetectionMode() == CoreConnectionSettings::UsePingTimeout)
161 Client::signalProxy()->setMaxHeartBeatCount(interval.toInt() / 30); // interval is 30 seconds
165 void CoreConnection::reconnectIntervalChanged(const QVariant &interval)
167 _reconnectTimer.setInterval(interval.toInt() * 1000);
173 void CoreConnection::solidNetworkStatusChanged(Solid::Networking::Status status)
175 CoreConnectionSettings s;
176 if (s.networkDetectionMode() != CoreConnectionSettings::UseSolid)
180 case Solid::Networking::Unknown:
181 case Solid::Networking::Connected:
182 //qDebug() << "Solid: Network status changed to connected or unknown";
183 if (state() == Disconnected) {
184 if (_wantReconnect && s.autoReconnect()) {
189 case Solid::Networking::Disconnecting:
190 case Solid::Networking::Unconnected:
191 if (state() != Disconnected && !isLocalConnection())
192 disconnectFromCore(tr("Network is down"), true);
202 bool CoreConnection::isEncrypted() const
204 return _peer && _peer->isSecure();
208 bool CoreConnection::isLocalConnection() const
212 if (currentAccount().isInternal())
214 if (_peer->isLocal())
221 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState)
225 switch (socketState) {
226 case QAbstractSocket::UnconnectedState:
227 text = tr("Disconnected");
229 case QAbstractSocket::HostLookupState:
230 text = tr("Looking up %1...").arg(currentAccount().hostName());
232 case QAbstractSocket::ConnectingState:
233 text = tr("Connecting to %1...").arg(currentAccount().hostName());
235 case QAbstractSocket::ConnectedState:
236 text = tr("Connected to %1").arg(currentAccount().hostName());
238 case QAbstractSocket::ClosingState:
239 text = tr("Disconnecting from %1...").arg(currentAccount().hostName());
246 emit progressTextChanged(text);
248 setState(socketState);
252 void CoreConnection::setState(QAbstractSocket::SocketState socketState)
254 ConnectionState state;
256 switch (socketState) {
257 case QAbstractSocket::UnconnectedState:
258 state = Disconnected;
260 case QAbstractSocket::HostLookupState:
261 case QAbstractSocket::ConnectingState:
262 case QAbstractSocket::ConnectedState: // we'll set it to Connected in connectionReady()
266 state = Disconnected;
273 void CoreConnection::setState(ConnectionState state)
275 if (state != _state) {
277 emit stateChanged(state);
278 if (state == Disconnected)
284 void CoreConnection::coreSocketError(QAbstractSocket::SocketError)
286 disconnectFromCore(_socket->errorString(), true);
290 void CoreConnection::coreSocketDisconnected()
292 _wasReconnect = !_requestedDisconnect;
293 resetConnection(true);
294 // FIXME handle disconnects gracefully
298 // note: this still expects the legacy protocol
299 // note²: after cleaning this up, we can probably get rid of _socket altogether
300 void CoreConnection::coreHasData(const QVariant &item)
302 QVariantMap msg = item.toMap();
303 if (!msg.contains("MsgType")) {
304 // This core is way too old and does not even speak our init protocol...
305 emit connectionErrorPopup(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
306 disconnectFromCore(QString(), false);
309 if (msg["MsgType"] == "ClientInitAck") {
312 else if (msg["MsgType"] == "ClientInitReject") {
313 emit connectionErrorPopup(msg["Error"].toString());
314 disconnectFromCore(QString(), false);
317 else if (msg["MsgType"] == "CoreSetupAck") {
318 emit coreSetupSuccess();
320 else if (msg["MsgType"] == "CoreSetupReject") {
321 emit coreSetupFailed(msg["Error"].toString());
323 else if (msg["MsgType"] == "ClientLoginReject") {
324 loginFailed(msg["Error"].toString());
326 else if (msg["MsgType"] == "ClientLoginAck") {
329 else if (msg["MsgType"] == "SessionInit") {
330 // that's it, let's hand over to the signal proxy
331 // if the connection is an orphan, the signalProxy adopts it.
332 // -> we don't need to care about it anymore
334 disconnect(_peer, 0, this, 0);
337 Client::signalProxy()->addPeer(_peer);
339 sessionStateReceived(msg["SessionState"].toMap());
342 disconnectFromCore(tr("Invalid data received from core"), false);
348 void CoreConnection::disconnectFromCore()
351 _requestedDisconnect = true;
352 disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
357 void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect)
360 _reconnectTimer.stop();
362 _wasReconnect = wantReconnect; // store if disconnect was requested
364 resetConnection(wantReconnect);
366 if (errorString.isEmpty())
367 emit connectionError(tr("Disconnected"));
369 emit connectionError(errorString);
373 void CoreConnection::resetConnection(bool wantReconnect)
379 _wantReconnect = wantReconnect;
382 disconnect(_socket, 0, this, 0);
383 disconnect(_peer, 0, this, 0);
386 if (_peer->parent() == this)
387 _peer->deleteLater(); // if it's not us, it belongs to the sigproxy which will delete it
388 _socket = 0; // socket is owned and will be deleted by RemoteConnection
392 disconnect(_socket, 0, this, 0);
393 _socket->deleteLater();
397 _requestedDisconnect = false;
399 _coreMsgBuffer.clear();
403 setProgressMaximum(-1); // disable
404 setState(Disconnected);
407 emit connectionMsg(tr("Disconnected from core."));
408 emit encrypted(false);
410 // initiate if a reconnect if appropriate
411 CoreConnectionSettings s;
412 if (wantReconnect && s.autoReconnect()) {
413 _reconnectTimer.start();
420 void CoreConnection::reconnectToCore()
422 if (currentAccount().isValid())
423 connectToCore(currentAccount().accountId());
427 bool CoreConnection::connectToCore(AccountId accId)
432 CoreAccountSettings s;
434 // FIXME: Don't force connection to internal core in mono client
435 if (Quassel::runMode() == Quassel::Monolithic) {
436 _account = accountModel()->account(accountModel()->internalAccount());
437 Q_ASSERT(_account.isValid());
440 if (!accId.isValid()) {
441 // check our settings and figure out what to do
442 if (!s.autoConnectOnStartup())
444 if (s.autoConnectToFixedAccount())
445 accId = s.autoConnectAccount();
447 accId = s.lastAccount();
448 if (!accId.isValid())
451 _account = accountModel()->account(accId);
452 if (!_account.accountId().isValid()) {
455 if (Quassel::runMode() != Quassel::Monolithic) {
456 if (_account.isInternal())
461 s.setLastAccount(accId);
462 connectToCurrentAccount();
467 void CoreConnection::connectToCurrentAccount()
469 resetConnection(false);
471 if (currentAccount().isInternal()) {
472 if (Quassel::runMode() != Quassel::Monolithic) {
473 qWarning() << "Cannot connect to internal core in client-only mode!";
476 emit startInternalCore();
478 InternalPeer *peer = new InternalPeer();
479 Client::instance()->signalProxy()->addPeer(peer); // sigproxy will take ownership
480 emit connectToInternalCore(peer);
485 CoreAccountSettings s;
489 QSslSocket *sock = new QSslSocket(this);
490 // make sure the warning is shown if we happen to connect without SSL support later
491 s.setAccountValue("ShowNoClientSslWarning", true);
493 if (_account.useSsl()) {
494 if (s.accountValue("ShowNoClientSslWarning", true).toBool()) {
495 bool accepted = false;
496 emit handleNoSslInClient(&accepted);
498 emit connectionError(tr("Unencrypted connection canceled"));
501 s.setAccountValue("ShowNoClientSslWarning", false);
504 QTcpSocket *sock = new QTcpSocket(this);
507 #ifndef QT_NO_NETWORKPROXY
508 if (_account.useProxy()) {
509 QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
510 sock->setProxy(proxy);
515 connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected()));
516 connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
517 connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError)));
518 connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
520 emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName()));
521 sock->connectToHost(_account.hostName(), _account.port());
525 void CoreConnection::coreSocketConnected()
527 // Create the connection which will handle the incoming data
529 _peer = new LegacyPeer(_socket, this);
530 connect(_peer, SIGNAL(dataReceived(QVariant)), SLOT(coreHasData(QVariant)));
531 connect(_peer, SIGNAL(transferProgress(int,int)), SLOT(updateProgress(int,int)));
533 // Phase One: Send client info and wait for core info
535 emit connectionMsg(tr("Synchronizing to core..."));
537 QVariantMap clientInit;
538 clientInit["MsgType"] = "ClientInit";
539 clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
540 clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
541 clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
542 clientInit["UseSsl"] = _account.useSsl();
543 #ifndef QT_NO_COMPRESS
544 clientInit["UseCompression"] = true;
546 clientInit["UseCompression"] = false;
549 qobject_cast<RemotePeer *>(_peer)->writeSocketData(clientInit);
553 void CoreConnection::clientInitAck(const QVariantMap &msg)
555 // Core has accepted our version info and sent its own. Let's see if we accept it as well...
556 uint ver = msg["ProtocolVersion"].toUInt();
557 if (ver < Quassel::buildInfo().clientNeedsProtocol) {
558 emit connectionErrorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
559 "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
560 disconnectFromCore(QString(), false);
564 Client::setCoreFeatures((Quassel::Features)msg["CoreFeatures"].toUInt());
566 #ifndef QT_NO_COMPRESS
567 if (msg["SupportsCompression"].toBool()) {
568 _socket->setProperty("UseCompression", true);
572 _coreMsgBuffer = msg;
575 CoreAccountSettings s;
576 if (currentAccount().useSsl()) {
577 if (msg["SupportSsl"].toBool()) {
578 // Make sure the warning is shown next time we don't have SSL in the core
579 s.setAccountValue("ShowNoCoreSslWarning", true);
581 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
583 connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
584 connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
585 sslSocket->startClientEncryption();
588 if (s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
589 bool accepted = false;
590 emit handleNoSslInCore(&accepted);
592 disconnectFromCore(tr("Unencrypted connection canceled"), false);
595 s.setAccountValue("ShowNoCoreSslWarning", false);
596 s.setAccountValue("SslCert", QString());
603 // if we use SSL we wait for the next step until every SSL warning has been cleared
610 void CoreConnection::sslSocketEncrypted()
612 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
615 if (!socket->sslErrors().count()) {
616 // Cert is valid, so we don't want to store it as known
617 // That way, a warning will appear in case it becomes invalid at some point
618 CoreAccountSettings s;
619 s.setAccountValue("SSLCert", QString());
622 emit encrypted(true);
627 void CoreConnection::sslErrors()
629 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
632 CoreAccountSettings s;
633 QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
635 if (knownDigest != socket->peerCertificate().digest()) {
636 bool accepted = false;
637 bool permanently = false;
638 emit handleSslErrors(socket, &accepted, &permanently);
641 disconnectFromCore(tr("Unencrypted connection canceled"), false);
646 s.setAccountValue("SslCert", socket->peerCertificate().digest());
648 s.setAccountValue("SslCert", QString());
651 socket->ignoreSslErrors();
655 #endif /* HAVE_SSL */
657 void CoreConnection::connectionReady()
660 emit connectionMsg(tr("Connected to %1").arg(currentAccount().accountName()));
662 if (!_coreMsgBuffer["Configured"].toBool()) {
664 emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
666 else if (_coreMsgBuffer["LoginEnabled"].toBool()) {
669 _coreMsgBuffer.clear();
673 void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember)
675 _account.setUser(user);
676 _account.setPassword(password);
677 _account.setStorePassword(remember);
682 void CoreConnection::loginToCore(const QString &prevError)
684 emit connectionMsg(tr("Logging in..."));
685 if (currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
687 emit userAuthenticationRequired(&_account, &valid, prevError); // *must* be a synchronous call
688 if (!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
689 disconnectFromCore(tr("Login canceled"), false);
694 QVariantMap clientLogin;
695 clientLogin["MsgType"] = "ClientLogin";
696 clientLogin["User"] = currentAccount().user();
697 clientLogin["Password"] = currentAccount().password();
698 qobject_cast<RemotePeer*>(_peer)->writeSocketData(clientLogin);
702 void CoreConnection::loginFailed(const QString &error)
708 void CoreConnection::loginSuccess()
710 updateProgress(0, 0);
712 // save current account data
713 _model->createOrUpdateAccount(currentAccount());
716 _reconnectTimer.stop();
718 setProgressText(tr("Receiving session state"));
719 setState(Synchronizing);
720 emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
724 void CoreConnection::sessionStateReceived(const QVariantMap &state)
726 updateProgress(100, 100);
732 void CoreConnection::internalSessionStateReceived(const QVariant &packedState)
734 updateProgress(100, 100);
736 setState(Synchronizing);
737 syncToCore(packedState.toMap());
741 void CoreConnection::syncToCore(const QVariantMap &sessionState)
743 if (sessionState.contains("CoreFeatures"))
744 Client::setCoreFeatures((Quassel::Features)sessionState["CoreFeatures"].toUInt());
746 setProgressText(tr("Receiving network states"));
747 updateProgress(0, 100);
750 foreach(QVariant vid, sessionState["Identities"].toList()) {
751 Client::instance()->coreIdentityCreated(vid.value<Identity>());
755 // FIXME: get rid of this crap -- why?
756 QVariantList bufferinfos = sessionState["BufferInfos"].toList();
757 NetworkModel *networkModel = Client::networkModel();
758 Q_ASSERT(networkModel);
759 foreach(QVariant vinfo, bufferinfos)
760 networkModel->bufferUpdated(vinfo.value<BufferInfo>()); // create BufferItems
762 QVariantList networkids = sessionState["NetworkIds"].toList();
764 // prepare sync progress thingys...
765 // FIXME: Care about removal of networks
766 _numNetsToSync = networkids.count();
767 updateProgress(0, _numNetsToSync);
769 // create network objects
770 foreach(QVariant networkid, networkids) {
771 NetworkId netid = networkid.value<NetworkId>();
772 if (Client::network(netid))
774 Network *net = new Network(netid, Client::instance());
775 _netsToSync.insert(net);
776 connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
777 connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
778 Client::addNetwork(net);
784 // this is also called for destroyed networks!
785 void CoreConnection::networkInitDone()
787 QObject *net = sender();
789 disconnect(net, 0, this, 0);
790 _netsToSync.remove(net);
791 updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
796 void CoreConnection::checkSyncState()
798 if (_netsToSync.isEmpty() && state() >= Synchronizing) {
799 setState(Synchronized);
800 setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
801 setProgressMaximum(-1);
807 void CoreConnection::doCoreSetup(const QVariant &setupData)
810 setup["MsgType"] = "CoreSetupData";
811 setup["SetupData"] = setupData;
812 qobject_cast<RemotePeer *>(_peer)->writeSocketData(setup);