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
37CoreConnection::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
52void 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
73CoreAccountModel *CoreConnection::accountModel() const
74{
75 return Client::coreAccountModel();
76}
77
78
79void CoreConnection::setProgressText(const QString &text)
80{
81 if (_progressText != text) {
82 _progressText = text;
83 emit progressTextChanged(text);
84 }
85}
86
87
88void CoreConnection::setProgressValue(int value)
89{
90 if (_progressValue != value) {
91 _progressValue = value;
92 emit progressValueChanged(value);
93 }
94}
95
96
97void CoreConnection::setProgressMinimum(int minimum)
98{
99 if (_progressMinimum != minimum) {
100 _progressMinimum = minimum;
101 emit progressRangeChanged(minimum, _progressMaximum);
102 }
103}
104
105
106void CoreConnection::setProgressMaximum(int maximum)
107{
108 if (_progressMaximum != maximum) {
109 _progressMaximum = maximum;
110 emit progressRangeChanged(_progressMinimum, maximum);
111 }
112}
113
114
115void 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
125void 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
146void 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
158void 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
166void CoreConnection::reconnectIntervalChanged(const QVariant &interval)
167{
168 _reconnectTimer.setInterval(interval.toInt() * 1000);
169}
170
171
172#ifdef HAVE_KDE
173
174void 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
202bool CoreConnection::isEncrypted() const
203{
204 return _peer && _peer->isSecure();
205}
206
207
208bool 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
223void CoreConnection::onConnectionReady()
224{
225 setState(Connected);
226}
227
228
229void 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
240void CoreConnection::coreSocketError(QAbstractSocket::SocketError error, const QString &errorString)
241{
242 Q_UNUSED(error)
243
244 disconnectFromCore(errorString, true);
245}
246
247
248void CoreConnection::coreSocketDisconnected()
249{
250 setState(Disconnected);
251 _wasReconnect = false;
252 resetConnection(_wantReconnect);
253}
254
255
256void CoreConnection::disconnectFromCore()
257{
258 disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
259}
260
261
262void 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
284void 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
327void CoreConnection::reconnectToCore()
328{
329 if (currentAccount().isValid()) {
330 _wasReconnect = true;
331 connectToCore(currentAccount().accountId());
332 }
333}
334
335
336bool 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
376void 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
429void CoreConnection::setupCore(const Protocol::SetupData &setupData)
430{
431 _authHandler->setupCore(setupData);
432}
433
434
435void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember)
436{
437 _authHandler->login(user, password, remember);
438}
439
440
441void 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
457void 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
476void 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
487void 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!
525void 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
536void 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}
545