Happy New Year!
[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::socketStateChanged(QAbstractSocket::SocketState socketState)
222 {
223     QString text;
224
225     switch (socketState) {
226     case QAbstractSocket::UnconnectedState:
227         text = tr("Disconnected");
228         break;
229     case QAbstractSocket::HostLookupState:
230         text = tr("Looking up %1...").arg(currentAccount().hostName());
231         break;
232     case QAbstractSocket::ConnectingState:
233         text = tr("Connecting to %1...").arg(currentAccount().hostName());
234         break;
235     case QAbstractSocket::ConnectedState:
236         text = tr("Connected to %1").arg(currentAccount().hostName());
237         break;
238     case QAbstractSocket::ClosingState:
239         text = tr("Disconnecting from %1...").arg(currentAccount().hostName());
240         break;
241     default:
242         break;
243     }
244
245     if (!text.isEmpty())
246         emit progressTextChanged(text);
247
248     setState(socketState);
249 }
250
251
252 void CoreConnection::setState(QAbstractSocket::SocketState socketState)
253 {
254     ConnectionState state;
255
256     switch (socketState) {
257     case QAbstractSocket::UnconnectedState:
258         state = Disconnected;
259         break;
260     case QAbstractSocket::HostLookupState:
261     case QAbstractSocket::ConnectingState:
262     case QAbstractSocket::ConnectedState: // we'll set it to Connected in connectionReady()
263         state = Connecting;
264         break;
265     default:
266         state = Disconnected;
267     }
268
269     setState(state);
270 }
271
272
273 void CoreConnection::onConnectionReady()
274 {
275     setState(Connected);
276 }
277
278
279 void CoreConnection::setState(ConnectionState state)
280 {
281     if (state != _state) {
282         _state = state;
283         emit stateChanged(state);
284         if (state == Disconnected)
285             emit disconnected();
286     }
287 }
288
289
290 void CoreConnection::coreSocketError(QAbstractSocket::SocketError error, const QString &errorString)
291 {
292     Q_UNUSED(error)
293
294     disconnectFromCore(errorString, true);
295 }
296
297
298 void CoreConnection::coreSocketDisconnected()
299 {
300     _wasReconnect = false;
301     resetConnection(_wantReconnect);
302     // FIXME handle disconnects gracefully
303 }
304
305
306 void CoreConnection::disconnectFromCore()
307 {
308     disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
309 }
310
311
312 void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect)
313 {
314     if (wantReconnect)
315         _reconnectTimer.start();
316     else
317         _reconnectTimer.stop();
318
319     _wantReconnect = wantReconnect; // store if disconnect was requested
320     _wasReconnect = false;
321
322     if (_authHandler)
323         _authHandler->close();
324     else if(_peer)
325         _peer->close();
326
327     if (errorString.isEmpty())
328         emit connectionError(tr("Disconnected"));
329     else
330         emit connectionError(errorString);
331 }
332
333
334 void CoreConnection::resetConnection(bool wantReconnect)
335 {
336     if (_resetting)
337         return;
338     _resetting = true;
339
340     _wantReconnect = wantReconnect;
341
342     if (_authHandler) {
343         disconnect(_authHandler, 0, this, 0);
344         _authHandler->close();
345         _authHandler->deleteLater();
346         _authHandler = 0;
347     }
348
349     if (_peer) {
350         disconnect(_peer, 0, this, 0);
351         // peer belongs to the sigproxy and thus gets deleted by it
352         _peer->close();
353         _peer = 0;
354     }
355
356     _netsToSync.clear();
357     _numNetsToSync = 0;
358
359     setProgressMaximum(-1); // disable
360     setState(Disconnected);
361     emit lagUpdated(-1);
362
363     emit connectionMsg(tr("Disconnected from core."));
364     emit encrypted(false);
365
366     // initiate if a reconnect if appropriate
367     CoreConnectionSettings s;
368     if (wantReconnect && s.autoReconnect()) {
369         _reconnectTimer.start();
370     }
371
372     _resetting = false;
373 }
374
375
376 void CoreConnection::reconnectToCore()
377 {
378     if (currentAccount().isValid()) {
379         _wasReconnect = true;
380         connectToCore(currentAccount().accountId());
381     }
382 }
383
384
385 bool CoreConnection::connectToCore(AccountId accId)
386 {
387     if (isConnected())
388         return false;
389
390     CoreAccountSettings s;
391
392     // FIXME: Don't force connection to internal core in mono client
393     if (Quassel::runMode() == Quassel::Monolithic) {
394         _account = accountModel()->account(accountModel()->internalAccount());
395         Q_ASSERT(_account.isValid());
396     }
397     else {
398         if (!accId.isValid()) {
399             // check our settings and figure out what to do
400             if (!s.autoConnectOnStartup())
401                 return false;
402             if (s.autoConnectToFixedAccount())
403                 accId = s.autoConnectAccount();
404             else
405                 accId = s.lastAccount();
406             if (!accId.isValid())
407                 return false;
408         }
409         _account = accountModel()->account(accId);
410         if (!_account.accountId().isValid()) {
411             return false;
412         }
413         if (Quassel::runMode() != Quassel::Monolithic) {
414             if (_account.isInternal())
415                 return false;
416         }
417     }
418
419     s.setLastAccount(accId);
420     connectToCurrentAccount();
421     return true;
422 }
423
424
425 void CoreConnection::connectToCurrentAccount()
426 {
427     if (_authHandler) {
428         qWarning() << Q_FUNC_INFO << "Already connected!";
429         return;
430     }
431
432     resetConnection(false);
433
434     if (currentAccount().isInternal()) {
435         if (Quassel::runMode() != Quassel::Monolithic) {
436             qWarning() << "Cannot connect to internal core in client-only mode!";
437             return;
438         }
439         emit startInternalCore();
440
441         InternalPeer *peer = new InternalPeer();
442         _peer = peer;
443         Client::instance()->signalProxy()->addPeer(peer); // sigproxy will take ownership
444         emit connectToInternalCore(peer);
445
446         return;
447     }
448
449     _authHandler = new ClientAuthHandler(currentAccount(), this);
450
451     connect(_authHandler, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
452     connect(_authHandler, SIGNAL(connectionReady()), SLOT(onConnectionReady()));
453     connect(_authHandler, SIGNAL(socketStateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
454     connect(_authHandler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(coreSocketError(QAbstractSocket::SocketError,QString)));
455     connect(_authHandler, SIGNAL(transferProgress(int,int)), SLOT(updateProgress(int,int)));
456     connect(_authHandler, SIGNAL(requestDisconnect(QString,bool)), SLOT(disconnectFromCore(QString,bool)));
457
458     connect(_authHandler, SIGNAL(errorMessage(QString)), SIGNAL(connectionError(QString)));
459     connect(_authHandler, SIGNAL(errorPopup(QString)), SIGNAL(connectionErrorPopup(QString)), Qt::QueuedConnection);
460     connect(_authHandler, SIGNAL(statusMessage(QString)), SIGNAL(connectionMsg(QString)));
461     connect(_authHandler, SIGNAL(encrypted(bool)), SIGNAL(encrypted(bool)));
462     connect(_authHandler, SIGNAL(startCoreSetup(QVariantList)), SIGNAL(startCoreSetup(QVariantList)));
463     connect(_authHandler, SIGNAL(coreSetupFailed(QString)), SIGNAL(coreSetupFailed(QString)));
464     connect(_authHandler, SIGNAL(coreSetupSuccessful()), SIGNAL(coreSetupSuccess()));
465     connect(_authHandler, SIGNAL(userAuthenticationRequired(CoreAccount*,bool*,QString)), SIGNAL(userAuthenticationRequired(CoreAccount*,bool*,QString)));
466     connect(_authHandler, SIGNAL(handleNoSslInClient(bool*)), SIGNAL(handleNoSslInClient(bool*)));
467     connect(_authHandler, SIGNAL(handleNoSslInCore(bool*)), SIGNAL(handleNoSslInCore(bool*)));
468 #ifdef HAVE_SSL
469     connect(_authHandler, SIGNAL(handleSslErrors(const QSslSocket*,bool*,bool*)), SIGNAL(handleSslErrors(const QSslSocket*,bool*,bool*)));
470 #endif
471
472     connect(_authHandler, SIGNAL(loginSuccessful(CoreAccount)), SLOT(onLoginSuccessful(CoreAccount)));
473     connect(_authHandler, SIGNAL(handshakeComplete(RemotePeer*,Protocol::SessionState)), SLOT(onHandshakeComplete(RemotePeer*,Protocol::SessionState)));
474
475     _authHandler->connectToCore();
476 }
477
478
479 void CoreConnection::setupCore(const Protocol::SetupData &setupData)
480 {
481     _authHandler->setupCore(setupData);
482 }
483
484
485 void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember)
486 {
487     _authHandler->login(user, password, remember);
488 }
489
490
491 void CoreConnection::onLoginSuccessful(const CoreAccount &account)
492 {
493     updateProgress(0, 0);
494
495     // save current account data
496     accountModel()->createOrUpdateAccount(account);
497     accountModel()->save();
498
499     _reconnectTimer.stop();
500
501     setProgressText(tr("Receiving session state"));
502     setState(Synchronizing);
503     emit connectionMsg(tr("Synchronizing to %1...").arg(account.accountName()));
504 }
505
506
507 void CoreConnection::onHandshakeComplete(RemotePeer *peer, const Protocol::SessionState &sessionState)
508 {
509     updateProgress(100, 100);
510
511     disconnect(_authHandler, 0, this, 0);
512     _authHandler->deleteLater();
513     _authHandler = 0;
514
515     _peer = peer;
516     connect(peer, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
517     connect(peer, SIGNAL(socketStateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
518     connect(peer, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(coreSocketError(QAbstractSocket::SocketError,QString)));
519
520     Client::signalProxy()->addPeer(_peer);  // sigproxy takes ownership of the peer!
521
522     syncToCore(sessionState);
523 }
524
525
526 void CoreConnection::internalSessionStateReceived(const Protocol::SessionState &sessionState)
527 {
528     updateProgress(100, 100);
529
530     Client::setCoreFeatures(Quassel::features()); // mono connection...
531
532     setState(Synchronizing);
533     syncToCore(sessionState);
534 }
535
536
537 void CoreConnection::syncToCore(const Protocol::SessionState &sessionState)
538 {
539     setProgressText(tr("Receiving network states"));
540     updateProgress(0, 100);
541
542     // create identities
543     foreach(const QVariant &vid, sessionState.identities) {
544         Client::instance()->coreIdentityCreated(vid.value<Identity>());
545     }
546
547     // create buffers
548     // FIXME: get rid of this crap -- why?
549     NetworkModel *networkModel = Client::networkModel();
550     Q_ASSERT(networkModel);
551     foreach(const QVariant &vinfo, sessionState.bufferInfos)
552         networkModel->bufferUpdated(vinfo.value<BufferInfo>());  // create BufferItems
553
554     // prepare sync progress thingys...
555     // FIXME: Care about removal of networks
556     _numNetsToSync = sessionState.networkIds.count();
557     updateProgress(0, _numNetsToSync);
558
559     // create network objects
560     foreach(const QVariant &networkid, sessionState.networkIds) {
561         NetworkId netid = networkid.value<NetworkId>();
562         if (Client::network(netid))
563             continue;
564         Network *net = new Network(netid, Client::instance());
565         _netsToSync.insert(net);
566         connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
567         connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
568         Client::addNetwork(net);
569     }
570     checkSyncState();
571 }
572
573
574 // this is also called for destroyed networks!
575 void CoreConnection::networkInitDone()
576 {
577     QObject *net = sender();
578     Q_ASSERT(net);
579     disconnect(net, 0, this, 0);
580     _netsToSync.remove(net);
581     updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
582     checkSyncState();
583 }
584
585
586 void CoreConnection::checkSyncState()
587 {
588     if (_netsToSync.isEmpty() && state() >= Synchronizing) {
589         setState(Synchronized);
590         setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
591         setProgressMaximum(-1);
592         emit synchronized();
593     }
594 }