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