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 | } |
545 | |