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