Add Qt-based connection detection
[quassel.git] / src / client / coreconnection.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2015 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  ***************************************************************************/
20
21 #include "coreconnection.h"
22
23 #include "client.h"
24 #include "clientauthhandler.h"
25 #include "clientsettings.h"
26 #include "coreaccountmodel.h"
27 #include "identity.h"
28 #include "internalpeer.h"
29 #include "network.h"
30 #include "networkmodel.h"
31 #include "quassel.h"
32 #include "signalproxy.h"
33 #include "util.h"
34
35 #include "protocols/legacy/legacypeer.h"
36
37 CoreConnection::CoreConnection(QObject *parent)
38     : QObject(parent),
39     _authHandler(0),
40     _state(Disconnected),
41     _wantReconnect(false),
42     _wasReconnect(false),
43     _progressMinimum(0),
44     _progressMaximum(-1),
45     _progressValue(-1),
46     _resetting(false),
47     _qNetworkConfigurationManager(0)
48 {
49     qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
50 }
51
52
53 void CoreConnection::init()
54 {
55     Client::signalProxy()->setHeartBeatInterval(30);
56     connect(Client::signalProxy(), SIGNAL(lagUpdated(int)), SIGNAL(lagUpdated(int)));
57
58     _reconnectTimer.setSingleShot(true);
59     connect(&_reconnectTimer, SIGNAL(timeout()), SLOT(reconnectTimeout()));
60
61 #ifdef HAVE_KDE4
62     connect(Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)),
63         SLOT(solidNetworkStatusChanged(Solid::Networking::Status)));
64 #endif
65     _qNetworkConfigurationManager = new QNetworkConfigurationManager(this);
66     connect(_qNetworkConfigurationManager, SIGNAL(onlineStateChanged(bool)), SLOT(onlineStateChanged(bool)));
67
68     CoreConnectionSettings s;
69     s.initAndNotify("PingTimeoutInterval", this, SLOT(pingTimeoutIntervalChanged(QVariant)), 60);
70     s.initAndNotify("ReconnectInterval", this, SLOT(reconnectIntervalChanged(QVariant)), 60);
71     s.notify("NetworkDetectionMode", this, SLOT(networkDetectionModeChanged(QVariant)));
72     networkDetectionModeChanged(s.networkDetectionMode());
73 }
74
75
76 CoreAccountModel *CoreConnection::accountModel() const
77 {
78     return Client::coreAccountModel();
79 }
80
81
82 void CoreConnection::setProgressText(const QString &text)
83 {
84     if (_progressText != text) {
85         _progressText = text;
86         emit progressTextChanged(text);
87     }
88 }
89
90
91 void CoreConnection::setProgressValue(int value)
92 {
93     if (_progressValue != value) {
94         _progressValue = value;
95         emit progressValueChanged(value);
96     }
97 }
98
99
100 void CoreConnection::setProgressMinimum(int minimum)
101 {
102     if (_progressMinimum != minimum) {
103         _progressMinimum = minimum;
104         emit progressRangeChanged(minimum, _progressMaximum);
105     }
106 }
107
108
109 void CoreConnection::setProgressMaximum(int maximum)
110 {
111     if (_progressMaximum != maximum) {
112         _progressMaximum = maximum;
113         emit progressRangeChanged(_progressMinimum, maximum);
114     }
115 }
116
117
118 void CoreConnection::updateProgress(int value, int max)
119 {
120     if (max != _progressMaximum) {
121         _progressMaximum = max;
122         emit progressRangeChanged(_progressMinimum, _progressMaximum);
123     }
124     setProgressValue(value);
125 }
126
127
128 void CoreConnection::reconnectTimeout()
129 {
130     if (!_peer) {
131         CoreConnectionSettings s;
132         if (_wantReconnect && s.autoReconnect()) {
133 #ifdef HAVE_KDE4
134             // If using Solid, we don't want to reconnect if we're offline
135             if (s.networkDetectionMode() == CoreConnectionSettings::UseSolid) {
136                 if (Solid::Networking::status() != Solid::Networking::Connected
137                     && Solid::Networking::status() != Solid::Networking::Unknown) {
138                     return;
139                 }
140             }
141 #endif /* HAVE_KDE4 */
142             // If using QNetworkConfigurationManager, ditto
143             if (s.networkDetectionMode() == CoreConnectionSettings::UseQNetworkConfigurationManager) {
144                if (!_qNetworkConfigurationManager->isOnline()) {
145                     return;
146                }
147             }
148
149             reconnectToCore();
150         }
151     }
152 }
153
154
155 void CoreConnection::networkDetectionModeChanged(const QVariant &vmode)
156 {
157     CoreConnectionSettings s;
158     CoreConnectionSettings::NetworkDetectionMode mode = (CoreConnectionSettings::NetworkDetectionMode)vmode.toInt();
159     if (mode == CoreConnectionSettings::UsePingTimeout)
160         Client::signalProxy()->setMaxHeartBeatCount(s.pingTimeoutInterval() / 30);
161     else {
162         Client::signalProxy()->setMaxHeartBeatCount(-1);
163     }
164 }
165
166
167 void CoreConnection::pingTimeoutIntervalChanged(const QVariant &interval)
168 {
169     CoreConnectionSettings s;
170     if (s.networkDetectionMode() == CoreConnectionSettings::UsePingTimeout)
171         Client::signalProxy()->setMaxHeartBeatCount(interval.toInt() / 30);  // interval is 30 seconds
172 }
173
174
175 void CoreConnection::reconnectIntervalChanged(const QVariant &interval)
176 {
177     _reconnectTimer.setInterval(interval.toInt() * 1000);
178 }
179
180
181 #ifdef HAVE_KDE4
182
183 void CoreConnection::solidNetworkStatusChanged(Solid::Networking::Status status)
184 {
185     CoreConnectionSettings s;
186     if (s.networkDetectionMode() != CoreConnectionSettings::UseSolid)
187         return;
188
189     switch (status) {
190     case Solid::Networking::Unknown:
191     case Solid::Networking::Connected:
192         //qDebug() << "Solid: Network status changed to connected or unknown";
193         if (state() == Disconnected) {
194             if (_wantReconnect && s.autoReconnect()) {
195                 reconnectToCore();
196             }
197         }
198         break;
199     case Solid::Networking::Disconnecting:
200     case Solid::Networking::Unconnected:
201         if (state() != Disconnected && !isLocalConnection())
202             disconnectFromCore(tr("Network is down"), true);
203         break;
204     default:
205         break;
206     }
207 }
208
209 #endif
210
211 void CoreConnection::onlineStateChanged(bool isOnline)
212 {
213     CoreConnectionSettings s;
214     if (s.networkDetectionMode() != CoreConnectionSettings::UseQNetworkConfigurationManager)
215         return;
216
217     if(isOnline) {
218         // qDebug() << "QNetworkConfigurationManager reports Online";
219         if (state() == Disconnected) {
220             if (_wantReconnect && s.autoReconnect()) {
221                 reconnectToCore();
222             }
223         }
224     } else {
225         // qDebug() << "QNetworkConfigurationManager reports Offline";
226         if (state() != Disconnected && !isLocalConnection())
227             disconnectFromCore(tr("Network is down"), true);
228     }
229 }
230
231 bool CoreConnection::isEncrypted() const
232 {
233     return _peer && _peer->isSecure();
234 }
235
236
237 bool CoreConnection::isLocalConnection() const
238 {
239     if (!isConnected())
240         return false;
241     if (currentAccount().isInternal())
242         return true;
243     if (_authHandler)
244         return _authHandler->isLocal();
245     if (_peer)
246         return _peer->isLocal();
247
248     return false;
249 }
250
251
252 void CoreConnection::onConnectionReady()
253 {
254     setState(Connected);
255 }
256
257
258 void CoreConnection::setState(ConnectionState state)
259 {
260     if (state != _state) {
261         _state = state;
262         emit stateChanged(state);
263         if (state == Disconnected)
264             emit disconnected();
265     }
266 }
267
268
269 void CoreConnection::coreSocketError(QAbstractSocket::SocketError error, const QString &errorString)
270 {
271     Q_UNUSED(error)
272
273     disconnectFromCore(errorString, true);
274 }
275
276
277 void CoreConnection::coreSocketDisconnected()
278 {
279     setState(Disconnected);
280     _wasReconnect = false;
281     resetConnection(_wantReconnect);
282 }
283
284
285 void CoreConnection::disconnectFromCore()
286 {
287     disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
288 }
289
290
291 void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect)
292 {
293     if (wantReconnect)
294         _reconnectTimer.start();
295     else
296         _reconnectTimer.stop();
297
298     _wantReconnect = wantReconnect; // store if disconnect was requested
299     _wasReconnect = false;
300
301     if (_authHandler)
302         _authHandler->close();
303     else if(_peer)
304         _peer->close();
305
306     if (errorString.isEmpty())
307         emit connectionError(tr("Disconnected"));
308     else
309         emit connectionError(errorString);
310 }
311
312
313 void CoreConnection::resetConnection(bool wantReconnect)
314 {
315     if (_resetting)
316         return;
317     _resetting = true;
318
319     _wantReconnect = wantReconnect;
320
321     if (_authHandler) {
322         disconnect(_authHandler, 0, this, 0);
323         _authHandler->close();
324         _authHandler->deleteLater();
325         _authHandler = 0;
326     }
327
328     if (_peer) {
329         disconnect(_peer, 0, this, 0);
330         // peer belongs to the sigproxy and thus gets deleted by it
331         _peer->close();
332         _peer = 0;
333     }
334
335     _netsToSync.clear();
336     _numNetsToSync = 0;
337
338     setProgressMaximum(-1); // disable
339     setState(Disconnected);
340     emit lagUpdated(-1);
341
342     emit connectionMsg(tr("Disconnected from core."));
343     emit encrypted(false);
344     setState(Disconnected);
345
346     // initiate if a reconnect if appropriate
347     CoreConnectionSettings s;
348     if (wantReconnect && s.autoReconnect()) {
349         _reconnectTimer.start();
350     }
351
352     _resetting = false;
353 }
354
355
356 void CoreConnection::reconnectToCore()
357 {
358     if (currentAccount().isValid()) {
359         _wasReconnect = true;
360         connectToCore(currentAccount().accountId());
361     }
362 }
363
364
365 bool CoreConnection::connectToCore(AccountId accId)
366 {
367     if (isConnected())
368         return false;
369
370     CoreAccountSettings s;
371
372     // FIXME: Don't force connection to internal core in mono client
373     if (Quassel::runMode() == Quassel::Monolithic) {
374         _account = accountModel()->account(accountModel()->internalAccount());
375         Q_ASSERT(_account.isValid());
376     }
377     else {
378         if (!accId.isValid()) {
379             // check our settings and figure out what to do
380             if (!s.autoConnectOnStartup())
381                 return false;
382             if (s.autoConnectToFixedAccount())
383                 accId = s.autoConnectAccount();
384             else
385                 accId = s.lastAccount();
386             if (!accId.isValid())
387                 return false;
388         }
389         _account = accountModel()->account(accId);
390         if (!_account.accountId().isValid()) {
391             return false;
392         }
393         if (Quassel::runMode() != Quassel::Monolithic) {
394             if (_account.isInternal())
395                 return false;
396         }
397     }
398
399     s.setLastAccount(accId);
400     connectToCurrentAccount();
401     return true;
402 }
403
404
405 void CoreConnection::connectToCurrentAccount()
406 {
407     if (_authHandler) {
408         qWarning() << Q_FUNC_INFO << "Already connected!";
409         return;
410     }
411
412     if (currentAccount().isInternal()) {
413         if (Quassel::runMode() != Quassel::Monolithic) {
414             qWarning() << "Cannot connect to internal core in client-only mode!";
415             return;
416         }
417         emit startInternalCore();
418
419         InternalPeer *peer = new InternalPeer();
420         _peer = peer;
421         Client::instance()->signalProxy()->addPeer(peer); // sigproxy will take ownership
422         emit connectToInternalCore(peer);
423         setState(Connected);
424
425         return;
426     }
427
428     _authHandler = new ClientAuthHandler(currentAccount(), this);
429
430     connect(_authHandler, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
431     connect(_authHandler, SIGNAL(connectionReady()), SLOT(onConnectionReady()));
432     connect(_authHandler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(coreSocketError(QAbstractSocket::SocketError,QString)));
433     connect(_authHandler, SIGNAL(transferProgress(int,int)), SLOT(updateProgress(int,int)));
434     connect(_authHandler, SIGNAL(requestDisconnect(QString,bool)), SLOT(disconnectFromCore(QString,bool)));
435
436     connect(_authHandler, SIGNAL(errorMessage(QString)), SIGNAL(connectionError(QString)));
437     connect(_authHandler, SIGNAL(errorPopup(QString)), SIGNAL(connectionErrorPopup(QString)), Qt::QueuedConnection);
438     connect(_authHandler, SIGNAL(statusMessage(QString)), SIGNAL(connectionMsg(QString)));
439     connect(_authHandler, SIGNAL(encrypted(bool)), SIGNAL(encrypted(bool)));
440     connect(_authHandler, SIGNAL(startCoreSetup(QVariantList)), SIGNAL(startCoreSetup(QVariantList)));
441     connect(_authHandler, SIGNAL(coreSetupFailed(QString)), SIGNAL(coreSetupFailed(QString)));
442     connect(_authHandler, SIGNAL(coreSetupSuccessful()), SIGNAL(coreSetupSuccess()));
443     connect(_authHandler, SIGNAL(userAuthenticationRequired(CoreAccount*,bool*,QString)), SIGNAL(userAuthenticationRequired(CoreAccount*,bool*,QString)));
444     connect(_authHandler, SIGNAL(handleNoSslInClient(bool*)), SIGNAL(handleNoSslInClient(bool*)));
445     connect(_authHandler, SIGNAL(handleNoSslInCore(bool*)), SIGNAL(handleNoSslInCore(bool*)));
446 #ifdef HAVE_SSL
447     connect(_authHandler, SIGNAL(handleSslErrors(const QSslSocket*,bool*,bool*)), SIGNAL(handleSslErrors(const QSslSocket*,bool*,bool*)));
448 #endif
449
450     connect(_authHandler, SIGNAL(loginSuccessful(CoreAccount)), SLOT(onLoginSuccessful(CoreAccount)));
451     connect(_authHandler, SIGNAL(handshakeComplete(RemotePeer*,Protocol::SessionState)), SLOT(onHandshakeComplete(RemotePeer*,Protocol::SessionState)));
452
453     setState(Connecting);
454     _authHandler->connectToCore();
455 }
456
457
458 void CoreConnection::setupCore(const Protocol::SetupData &setupData)
459 {
460     _authHandler->setupCore(setupData);
461 }
462
463
464 void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember)
465 {
466     _authHandler->login(user, password, remember);
467 }
468
469
470 void CoreConnection::onLoginSuccessful(const CoreAccount &account)
471 {
472     updateProgress(0, 0);
473
474     // save current account data
475     accountModel()->createOrUpdateAccount(account);
476     accountModel()->save();
477
478     _reconnectTimer.stop();
479
480     setProgressText(tr("Receiving session state"));
481     setState(Synchronizing);
482     emit connectionMsg(tr("Synchronizing to %1...").arg(account.accountName()));
483 }
484
485
486 void CoreConnection::onHandshakeComplete(RemotePeer *peer, const Protocol::SessionState &sessionState)
487 {
488     updateProgress(100, 100);
489
490     disconnect(_authHandler, 0, this, 0);
491     _authHandler->deleteLater();
492     _authHandler = 0;
493
494     _peer = peer;
495     connect(peer, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
496     connect(peer, SIGNAL(statusMessage(QString)), SIGNAL(connectionMsg(QString)));
497     connect(peer, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(coreSocketError(QAbstractSocket::SocketError,QString)));
498
499     Client::signalProxy()->addPeer(_peer);  // sigproxy takes ownership of the peer!
500
501     syncToCore(sessionState);
502 }
503
504
505 void CoreConnection::internalSessionStateReceived(const Protocol::SessionState &sessionState)
506 {
507     updateProgress(100, 100);
508
509     Client::setCoreFeatures(Quassel::features()); // mono connection...
510
511     setState(Synchronizing);
512     syncToCore(sessionState);
513 }
514
515
516 void CoreConnection::syncToCore(const Protocol::SessionState &sessionState)
517 {
518     setProgressText(tr("Receiving network states"));
519     updateProgress(0, 100);
520
521     // create identities
522     foreach(const QVariant &vid, sessionState.identities) {
523         Client::instance()->coreIdentityCreated(vid.value<Identity>());
524     }
525
526     // create buffers
527     // FIXME: get rid of this crap -- why?
528     NetworkModel *networkModel = Client::networkModel();
529     Q_ASSERT(networkModel);
530     foreach(const QVariant &vinfo, sessionState.bufferInfos)
531         networkModel->bufferUpdated(vinfo.value<BufferInfo>());  // create BufferItems
532
533     // prepare sync progress thingys...
534     // FIXME: Care about removal of networks
535     _numNetsToSync = sessionState.networkIds.count();
536     updateProgress(0, _numNetsToSync);
537
538     // create network objects
539     foreach(const QVariant &networkid, sessionState.networkIds) {
540         NetworkId netid = networkid.value<NetworkId>();
541         if (Client::network(netid))
542             continue;
543         Network *net = new Network(netid, Client::instance());
544         _netsToSync.insert(net);
545         connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
546         connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
547         Client::addNetwork(net);
548     }
549     checkSyncState();
550 }
551
552
553 // this is also called for destroyed networks!
554 void CoreConnection::networkInitDone()
555 {
556     QObject *net = sender();
557     Q_ASSERT(net);
558     disconnect(net, 0, this, 0);
559     _netsToSync.remove(net);
560     updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
561     checkSyncState();
562 }
563
564
565 void CoreConnection::checkSyncState()
566 {
567     if (_netsToSync.isEmpty() && state() >= Synchronizing) {
568         setState(Synchronized);
569         setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
570         setProgressMaximum(-1);
571         emit synchronized();
572     }
573 }