1 /***************************************************************************
2 * Copyright (C) 2009 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 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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");
52 void CoreConnection::init() {
53 Client::signalProxy()->setHeartBeatInterval(30);
54 connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
55 connect(Client::signalProxy(), SIGNAL(lagUpdated(int)), SIGNAL(lagUpdated(int)));
57 _reconnectTimer.setSingleShot(true);
58 connect(&_reconnectTimer, SIGNAL(timeout()), SLOT(reconnectTimeout()));
61 connect(Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)),
62 SLOT(solidNetworkStatusChanged(Solid::Networking::Status)));
65 CoreConnectionSettings s;
66 s.initAndNotify("PingTimeoutInterval", this, SLOT(pingTimeoutIntervalChanged(QVariant)), 60);
67 s.initAndNotify("ReconnectInterval", this, SLOT(reconnectIntervalChanged(QVariant)), 60);
68 s.notify("NetworkDetectionMode", this, SLOT(networkDetectionModeChanged(QVariant)));
69 networkDetectionModeChanged(s.networkDetectionMode());
72 void CoreConnection::setProgressText(const QString &text) {
73 if(_progressText != text) {
75 emit progressTextChanged(text);
79 void CoreConnection::setProgressValue(int value) {
80 if(_progressValue != value) {
81 _progressValue = value;
82 emit progressValueChanged(value);
86 void CoreConnection::setProgressMinimum(int minimum) {
87 if(_progressMinimum != minimum) {
88 _progressMinimum = minimum;
89 emit progressRangeChanged(minimum, _progressMaximum);
93 void CoreConnection::setProgressMaximum(int maximum) {
94 if(_progressMaximum != maximum) {
95 _progressMaximum = maximum;
96 emit progressRangeChanged(_progressMinimum, maximum);
100 void CoreConnection::updateProgress(int value, int max) {
101 if(max != _progressMaximum) {
102 _progressMaximum = max;
103 emit progressRangeChanged(_progressMinimum, _progressMaximum);
105 setProgressValue(value);
108 void CoreConnection::reconnectTimeout() {
110 CoreConnectionSettings s;
111 if(_wantReconnect && s.autoReconnect()) {
114 // If using Solid, we don't want to reconnect if we're offline
115 if(s.networkDetectionMode() == CoreConnectionSettings::UseSolid) {
116 if(Solid::Networking::status() != Solid::Networking::Connected
117 && Solid::Networking::status() != Solid::Networking::Unknown) {
121 #endif /* HAVE_KDE */
128 void CoreConnection::networkDetectionModeChanged(const QVariant &vmode) {
129 CoreConnectionSettings s;
130 CoreConnectionSettings::NetworkDetectionMode mode = (CoreConnectionSettings::NetworkDetectionMode)vmode.toInt();
131 if(mode == CoreConnectionSettings::UsePingTimeout)
132 Client::signalProxy()->setMaxHeartBeatCount(s.pingTimeoutInterval() / 30);
134 Client::signalProxy()->setMaxHeartBeatCount(-1);
138 void CoreConnection::pingTimeoutIntervalChanged(const QVariant &interval) {
139 CoreConnectionSettings s;
140 if(s.networkDetectionMode() == CoreConnectionSettings::UsePingTimeout)
141 Client::signalProxy()->setMaxHeartBeatCount(interval.toInt() / 30); // interval is 30 seconds
144 void CoreConnection::reconnectIntervalChanged(const QVariant &interval) {
145 _reconnectTimer.setInterval(interval.toInt() * 1000);
150 void CoreConnection::solidNetworkStatusChanged(Solid::Networking::Status status) {
151 CoreConnectionSettings s;
152 if(s.networkDetectionMode() != CoreConnectionSettings::UseSolid)
156 case Solid::Networking::Unknown:
157 case Solid::Networking::Connected:
158 //qDebug() << "Solid: Network status changed to connected or unknown";
159 if(state() == Disconnected) {
160 if(_wantReconnect && s.autoReconnect()) {
165 case Solid::Networking::Disconnecting:
166 case Solid::Networking::Unconnected:
167 if(state() != Disconnected && !isLocalConnection())
168 disconnectFromCore(tr("Network is down"), true);
177 bool CoreConnection::isEncrypted() const {
181 QSslSocket *sock = qobject_cast<QSslSocket *>(_socket);
182 return isConnected() && sock && sock->isEncrypted();
186 bool CoreConnection::isLocalConnection() const {
189 if(currentAccount().isInternal())
191 if(_socket->peerAddress().isInSubnet(QHostAddress::LocalHost, 0x00ffffff))
197 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
200 switch(socketState) {
201 case QAbstractSocket::UnconnectedState:
202 text = tr("Disconnected");
204 case QAbstractSocket::HostLookupState:
205 text = tr("Looking up %1...").arg(currentAccount().hostName());
207 case QAbstractSocket::ConnectingState:
208 text = tr("Connecting to %1...").arg(currentAccount().hostName());
210 case QAbstractSocket::ConnectedState:
211 text = tr("Connected to %1").arg(currentAccount().hostName());
213 case QAbstractSocket::ClosingState:
214 text = tr("Disconnecting from %1...").arg(currentAccount().hostName());
221 emit progressTextChanged(text);
223 setState(socketState);
226 void CoreConnection::setState(QAbstractSocket::SocketState socketState) {
227 ConnectionState state;
229 switch(socketState) {
230 case QAbstractSocket::UnconnectedState:
231 state = Disconnected;
233 case QAbstractSocket::HostLookupState:
234 case QAbstractSocket::ConnectingState:
235 case QAbstractSocket::ConnectedState: // we'll set it to Connected in connectionReady()
239 state = Disconnected;
245 void CoreConnection::setState(ConnectionState state) {
246 if(state != _state) {
248 emit stateChanged(state);
249 if(state == Disconnected)
254 void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
255 qDebug() << "coreSocketError" << _socket << _socket->errorString();
256 disconnectFromCore(_socket->errorString(), true);
259 void CoreConnection::coreSocketDisconnected() {
260 // qDebug() << Q_FUNC_INFO;
261 _wasReconnect = !_requestedDisconnect;
262 resetConnection(true);
263 // FIXME handle disconnects gracefully
266 void CoreConnection::coreHasData() {
268 while(SignalProxy::readDataFromDevice(_socket, _blockSize, item)) {
269 QVariantMap msg = item.toMap();
270 if(!msg.contains("MsgType")) {
271 // This core is way too old and does not even speak our init protocol...
272 emit connectionErrorPopup(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
273 disconnectFromCore(QString(), false);
276 if(msg["MsgType"] == "ClientInitAck") {
278 } else if(msg["MsgType"] == "ClientInitReject") {
279 emit connectionErrorPopup(msg["Error"].toString());
280 disconnectFromCore(QString(), false);
282 } else if(msg["MsgType"] == "CoreSetupAck") {
283 emit coreSetupSuccess();
284 } else if(msg["MsgType"] == "CoreSetupReject") {
285 emit coreSetupFailed(msg["Error"].toString());
286 } else if(msg["MsgType"] == "ClientLoginReject") {
287 loginFailed(msg["Error"].toString());
288 } else if(msg["MsgType"] == "ClientLoginAck") {
290 } else if(msg["MsgType"] == "SessionInit") {
291 // that's it, let's hand over to the signal proxy
292 // if the socket is an orphan, the signalProxy adopts it.
293 // -> we don't need to care about it anymore
294 _socket->setParent(0);
295 Client::signalProxy()->addPeer(_socket);
297 sessionStateReceived(msg["SessionState"].toMap());
298 break; // this is definitively the last message we process here!
300 disconnectFromCore(tr("Invalid data received from core"), false);
305 updateProgress(_socket->bytesAvailable(), _blockSize);
309 void CoreConnection::disconnectFromCore() {
310 _requestedDisconnect = true;
311 disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
314 void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect) {
316 _reconnectTimer.stop();
318 _wasReconnect = wantReconnect; // store if disconnect was requested
320 if(errorString.isEmpty())
321 emit connectionError(tr("Disconnected"));
323 emit connectionError(errorString);
325 Client::signalProxy()->removeAllPeers();
326 resetConnection(wantReconnect);
329 void CoreConnection::resetConnection(bool wantReconnect) {
330 _wantReconnect = wantReconnect;
333 disconnect(_socket, 0, this, 0);
334 _socket->deleteLater();
337 _requestedDisconnect = false;
340 _coreMsgBuffer.clear();
345 setProgressMaximum(-1); // disable
346 setState(Disconnected);
349 emit connectionMsg(tr("Disconnected from core."));
350 emit encrypted(false);
352 // initiate if a reconnect if appropriate
353 CoreConnectionSettings s;
354 if(wantReconnect && s.autoReconnect()) {
355 _reconnectTimer.start();
359 void CoreConnection::reconnectToCore() {
360 if(currentAccount().isValid())
361 connectToCore(currentAccount().accountId());
364 bool CoreConnection::connectToCore(AccountId accId) {
368 CoreAccountSettings s;
370 // FIXME: Don't force connection to internal core in mono client
371 if(Quassel::runMode() == Quassel::Monolithic) {
372 _account = accountModel()->account(accountModel()->internalAccount());
373 Q_ASSERT(_account.isValid());
375 if(!accId.isValid()) {
376 // check our settings and figure out what to do
377 if(!s.autoConnectOnStartup())
379 if(s.autoConnectToFixedAccount())
380 accId = s.autoConnectAccount();
382 accId = s.lastAccount();
386 _account = accountModel()->account(accId);
387 if(!_account.accountId().isValid()) {
390 if(Quassel::runMode() != Quassel::Monolithic) {
391 if(_account.isInternal())
396 s.setLastAccount(accId);
397 connectToCurrentAccount();
401 void CoreConnection::connectToCurrentAccount() {
402 resetConnection(false);
404 if(currentAccount().isInternal()) {
405 if(Quassel::runMode() != Quassel::Monolithic) {
406 qWarning() << "Cannot connect to internal core in client-only mode!";
409 emit startInternalCore();
410 emit connectToInternalCore(Client::instance()->signalProxy());
414 CoreAccountSettings s;
418 QSslSocket *sock = new QSslSocket(Client::instance());
419 // make sure the warning is shown if we happen to connect without SSL support later
420 s.setAccountValue("ShowNoClientSslWarning", true);
422 if(_account.useSsl()) {
423 if(s.accountValue("ShowNoClientSslWarning", true).toBool()) {
424 bool accepted = false;
425 emit handleNoSslInClient(&accepted);
427 emit connectionError(tr("Unencrypted connection canceled"));
430 s.setAccountValue("ShowNoClientSslWarning", false);
433 QTcpSocket *sock = new QTcpSocket(Client::instance());
436 #ifndef QT_NO_NETWORKPROXY
437 if(_account.useProxy()) {
438 QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
439 sock->setProxy(proxy);
444 connect(sock, SIGNAL(readyRead()), SLOT(coreHasData()));
445 connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected()));
446 connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
447 connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError)));
448 connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
450 emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName()));
451 sock->connectToHost(_account.hostName(), _account.port());
454 void CoreConnection::coreSocketConnected() {
455 // Phase One: Send client info and wait for core info
457 emit connectionMsg(tr("Synchronizing to core..."));
459 QVariantMap clientInit;
460 clientInit["MsgType"] = "ClientInit";
461 clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
462 clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
463 clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
464 clientInit["UseSsl"] = _account.useSsl();
465 #ifndef QT_NO_COMPRESS
466 clientInit["UseCompression"] = true;
468 clientInit["UseCompression"] = false;
471 SignalProxy::writeDataToDevice(_socket, clientInit);
474 void CoreConnection::clientInitAck(const QVariantMap &msg) {
475 // Core has accepted our version info and sent its own. Let's see if we accept it as well...
476 uint ver = msg["ProtocolVersion"].toUInt();
477 if(ver < Quassel::buildInfo().clientNeedsProtocol) {
478 emit connectionErrorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
479 "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
480 disconnectFromCore(QString(), false);
484 Client::setCoreFeatures((Quassel::Features)msg["CoreFeatures"].toUInt());
486 #ifndef QT_NO_COMPRESS
487 if(msg["SupportsCompression"].toBool()) {
488 _socket->setProperty("UseCompression", true);
492 _coreMsgBuffer = msg;
495 CoreAccountSettings s;
496 if(currentAccount().useSsl()) {
497 if(msg["SupportSsl"].toBool()) {
498 // Make sure the warning is shown next time we don't have SSL in the core
499 s.setAccountValue("ShowNoCoreSslWarning", true);
501 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
503 connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
504 connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
505 sslSocket->startClientEncryption();
507 if(s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
508 bool accepted = false;
509 emit handleNoSslInCore(&accepted);
511 disconnectFromCore(tr("Unencrypted connection canceled"), false);
514 s.setAccountValue("ShowNoCoreSslWarning", false);
515 s.setAccountValue("SslCert", QString());
522 // if we use SSL we wait for the next step until every SSL warning has been cleared
528 void CoreConnection::sslSocketEncrypted() {
529 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
532 if(!socket->sslErrors().count()) {
533 // Cert is valid, so we don't want to store it as known
534 // That way, a warning will appear in case it becomes invalid at some point
535 CoreAccountSettings s;
536 s.setAccountValue("SSLCert", QString());
539 emit encrypted(true);
543 void CoreConnection::sslErrors() {
544 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
547 CoreAccountSettings s;
548 QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
550 if(knownDigest != socket->peerCertificate().digest()) {
551 bool accepted = false;
552 bool permanently = false;
553 emit handleSslErrors(socket, &accepted, &permanently);
556 disconnectFromCore(tr("Unencrypted connection canceled"), false);
561 s.setAccountValue("SslCert", socket->peerCertificate().digest());
563 s.setAccountValue("SslCert", QString());
566 socket->ignoreSslErrors();
569 #endif /* HAVE_SSL */
571 void CoreConnection::connectionReady() {
573 emit connectionMsg(tr("Connected to %1").arg(currentAccount().accountName()));
575 if(!_coreMsgBuffer["Configured"].toBool()) {
577 emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
578 } else if(_coreMsgBuffer["LoginEnabled"].toBool()) {
581 _coreMsgBuffer.clear();
584 void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember) {
585 _account.setUser(user);
586 _account.setPassword(password);
587 _account.setStorePassword(remember);
591 void CoreConnection::loginToCore(const QString &prevError) {
592 emit connectionMsg(tr("Logging in..."));
593 if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
595 emit userAuthenticationRequired(&_account, &valid, prevError); // *must* be a synchronous call
596 if(!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
597 disconnectFromCore(tr("Login canceled"), false);
602 QVariantMap clientLogin;
603 clientLogin["MsgType"] = "ClientLogin";
604 clientLogin["User"] = currentAccount().user();
605 clientLogin["Password"] = currentAccount().password();
606 SignalProxy::writeDataToDevice(_socket, clientLogin);
609 void CoreConnection::loginFailed(const QString &error) {
613 void CoreConnection::loginSuccess() {
614 updateProgress(0, 0);
616 // save current account data
617 _model->createOrUpdateAccount(currentAccount());
620 _reconnectTimer.stop();
622 setProgressText(tr("Receiving session state"));
623 setState(Synchronizing);
624 emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
627 void CoreConnection::sessionStateReceived(const QVariantMap &state) {
628 updateProgress(100, 100);
630 // rest of communication happens through SignalProxy...
631 disconnect(_socket, SIGNAL(readyRead()), this, 0);
632 disconnect(_socket, SIGNAL(connected()), this, 0);
637 void CoreConnection::internalSessionStateReceived(const QVariant &packedState) {
638 updateProgress(100, 100);
640 setState(Synchronizing);
641 syncToCore(packedState.toMap());
644 void CoreConnection::syncToCore(const QVariantMap &sessionState) {
645 if(sessionState.contains("CoreFeatures"))
646 Client::setCoreFeatures((Quassel::Features)sessionState["CoreFeatures"].toUInt());
648 setProgressText(tr("Receiving network states"));
649 updateProgress(0, 100);
652 foreach(QVariant vid, sessionState["Identities"].toList()) {
653 Client::instance()->coreIdentityCreated(vid.value<Identity>());
657 // FIXME: get rid of this crap -- why?
658 QVariantList bufferinfos = sessionState["BufferInfos"].toList();
659 NetworkModel *networkModel = Client::networkModel();
660 Q_ASSERT(networkModel);
661 foreach(QVariant vinfo, bufferinfos)
662 networkModel->bufferUpdated(vinfo.value<BufferInfo>()); // create BufferItems
664 QVariantList networkids = sessionState["NetworkIds"].toList();
666 // prepare sync progress thingys...
667 // FIXME: Care about removal of networks
668 _numNetsToSync = networkids.count();
669 updateProgress(0, _numNetsToSync);
671 // create network objects
672 foreach(QVariant networkid, networkids) {
673 NetworkId netid = networkid.value<NetworkId>();
674 if(Client::network(netid))
676 Network *net = new Network(netid, Client::instance());
677 _netsToSync.insert(net);
678 connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
679 connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
680 Client::addNetwork(net);
685 // this is also called for destroyed networks!
686 void CoreConnection::networkInitDone() {
687 QObject *net = sender();
689 disconnect(net, 0, this, 0);
690 _netsToSync.remove(net);
691 updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
695 void CoreConnection::checkSyncState() {
696 if(_netsToSync.isEmpty() && state() >= Synchronizing) {
697 setState(Synchronized);
698 setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
699 setProgressMaximum(-1);
704 void CoreConnection::doCoreSetup(const QVariant &setupData) {
706 setup["MsgType"] = "CoreSetupData";
707 setup["SetupData"] = setupData;
708 SignalProxy::writeDataToDevice(_socket, setup);