ab42b74cc23fc46f72e5d28efddaace415889af3
[quassel.git] / src / client / coreconnection.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2013 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 #ifndef QT_NO_NETWORKPROXY
24 #  include <QNetworkProxy>
25 #endif
26
27 #include "client.h"
28 #include "clientsettings.h"
29 #include "coreaccountmodel.h"
30 #include "identity.h"
31 #include "internalpeer.h"
32 #include "network.h"
33 #include "networkmodel.h"
34 #include "quassel.h"
35 #include "signalproxy.h"
36 #include "util.h"
37
38 #include "protocols/legacy/legacypeer.h"
39
40 CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent)
41     : QObject(parent),
42     _model(model),
43     _state(Disconnected),
44     _wantReconnect(false),
45     _wasReconnect(false),
46     _progressMinimum(0),
47     _progressMaximum(-1),
48     _progressValue(-1),
49     _resetting(false)
50 {
51     qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
52 }
53
54
55 void CoreConnection::init()
56 {
57     Client::signalProxy()->setHeartBeatInterval(30);
58     connect(Client::signalProxy(), SIGNAL(lagUpdated(int)), SIGNAL(lagUpdated(int)));
59
60     _reconnectTimer.setSingleShot(true);
61     connect(&_reconnectTimer, SIGNAL(timeout()), SLOT(reconnectTimeout()));
62
63 #ifdef HAVE_KDE
64     connect(Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)),
65         SLOT(solidNetworkStatusChanged(Solid::Networking::Status)));
66 #endif
67
68     CoreConnectionSettings s;
69     s.initAndNotify("PingTimeoutInterval", this, SLOT(pingTimeoutIntervalChanged(QVariant)), 60);
70     s.initAndNotify("ReconnectInterval", this, SLOT(reconnectIntervalChanged(QVariant)), 60);
71     s.notify("NetworkDetectionMode", this, SLOT(networkDetectionModeChanged(QVariant)));
72     networkDetectionModeChanged(s.networkDetectionMode());
73 }
74
75
76 void CoreConnection::setProgressText(const QString &text)
77 {
78     if (_progressText != text) {
79         _progressText = text;
80         emit progressTextChanged(text);
81     }
82 }
83
84
85 void CoreConnection::setProgressValue(int value)
86 {
87     if (_progressValue != value) {
88         _progressValue = value;
89         emit progressValueChanged(value);
90     }
91 }
92
93
94 void CoreConnection::setProgressMinimum(int minimum)
95 {
96     if (_progressMinimum != minimum) {
97         _progressMinimum = minimum;
98         emit progressRangeChanged(minimum, _progressMaximum);
99     }
100 }
101
102
103 void CoreConnection::setProgressMaximum(int maximum)
104 {
105     if (_progressMaximum != maximum) {
106         _progressMaximum = maximum;
107         emit progressRangeChanged(_progressMinimum, maximum);
108     }
109 }
110
111
112 void CoreConnection::updateProgress(int value, int max)
113 {
114     if (max != _progressMaximum) {
115         _progressMaximum = max;
116         emit progressRangeChanged(_progressMinimum, _progressMaximum);
117     }
118     setProgressValue(value);
119 }
120
121
122 void CoreConnection::reconnectTimeout()
123 {
124     if (!_peer) {
125         CoreConnectionSettings s;
126         if (_wantReconnect && s.autoReconnect()) {
127 #ifdef HAVE_KDE
128             // If using Solid, we don't want to reconnect if we're offline
129             if (s.networkDetectionMode() == CoreConnectionSettings::UseSolid) {
130                 if (Solid::Networking::status() != Solid::Networking::Connected
131                     && Solid::Networking::status() != Solid::Networking::Unknown) {
132                     return;
133                 }
134             }
135 #endif /* HAVE_KDE */
136
137             reconnectToCore();
138         }
139     }
140 }
141
142
143 void CoreConnection::networkDetectionModeChanged(const QVariant &vmode)
144 {
145     CoreConnectionSettings s;
146     CoreConnectionSettings::NetworkDetectionMode mode = (CoreConnectionSettings::NetworkDetectionMode)vmode.toInt();
147     if (mode == CoreConnectionSettings::UsePingTimeout)
148         Client::signalProxy()->setMaxHeartBeatCount(s.pingTimeoutInterval() / 30);
149     else {
150         Client::signalProxy()->setMaxHeartBeatCount(-1);
151     }
152 }
153
154
155 void CoreConnection::pingTimeoutIntervalChanged(const QVariant &interval)
156 {
157     CoreConnectionSettings s;
158     if (s.networkDetectionMode() == CoreConnectionSettings::UsePingTimeout)
159         Client::signalProxy()->setMaxHeartBeatCount(interval.toInt() / 30);  // interval is 30 seconds
160 }
161
162
163 void CoreConnection::reconnectIntervalChanged(const QVariant &interval)
164 {
165     _reconnectTimer.setInterval(interval.toInt() * 1000);
166 }
167
168
169 #ifdef HAVE_KDE
170
171 void CoreConnection::solidNetworkStatusChanged(Solid::Networking::Status status)
172 {
173     CoreConnectionSettings s;
174     if (s.networkDetectionMode() != CoreConnectionSettings::UseSolid)
175         return;
176
177     switch (status) {
178     case Solid::Networking::Unknown:
179     case Solid::Networking::Connected:
180         //qDebug() << "Solid: Network status changed to connected or unknown";
181         if (state() == Disconnected) {
182             if (_wantReconnect && s.autoReconnect()) {
183                 reconnectToCore();
184             }
185         }
186         break;
187     case Solid::Networking::Disconnecting:
188     case Solid::Networking::Unconnected:
189         if (state() != Disconnected && !isLocalConnection())
190             disconnectFromCore(tr("Network is down"), true);
191         break;
192     default:
193         break;
194     }
195 }
196
197
198 #endif
199
200 bool CoreConnection::isEncrypted() const
201 {
202     return _peer && _peer->isSecure();
203 }
204
205
206 bool CoreConnection::isLocalConnection() const
207 {
208     if (!isConnected())
209         return false;
210     if (currentAccount().isInternal())
211         return true;
212     if (_peer->isLocal())
213         return true;
214
215     return false;
216 }
217
218
219 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState)
220 {
221     QString text;
222
223     switch (socketState) {
224     case QAbstractSocket::UnconnectedState:
225         text = tr("Disconnected");
226         break;
227     case QAbstractSocket::HostLookupState:
228         text = tr("Looking up %1...").arg(currentAccount().hostName());
229         break;
230     case QAbstractSocket::ConnectingState:
231         text = tr("Connecting to %1...").arg(currentAccount().hostName());
232         break;
233     case QAbstractSocket::ConnectedState:
234         text = tr("Connected to %1").arg(currentAccount().hostName());
235         break;
236     case QAbstractSocket::ClosingState:
237         text = tr("Disconnecting from %1...").arg(currentAccount().hostName());
238         break;
239     default:
240         break;
241     }
242
243     if (!text.isEmpty())
244         emit progressTextChanged(text);
245
246     setState(socketState);
247 }
248
249
250 void CoreConnection::setState(QAbstractSocket::SocketState socketState)
251 {
252     ConnectionState state;
253
254     switch (socketState) {
255     case QAbstractSocket::UnconnectedState:
256         state = Disconnected;
257         break;
258     case QAbstractSocket::HostLookupState:
259     case QAbstractSocket::ConnectingState:
260     case QAbstractSocket::ConnectedState: // we'll set it to Connected in connectionReady()
261         state = Connecting;
262         break;
263     default:
264         state = Disconnected;
265     }
266
267     setState(state);
268 }
269
270
271 void CoreConnection::setState(ConnectionState state)
272 {
273     if (state != _state) {
274         _state = state;
275         emit stateChanged(state);
276         if (state == Disconnected)
277             emit disconnected();
278     }
279 }
280
281
282 void CoreConnection::coreSocketError(QAbstractSocket::SocketError)
283 {
284     disconnectFromCore(_socket->errorString(), true);
285 }
286
287
288 void CoreConnection::coreSocketDisconnected()
289 {
290     _wasReconnect = false;
291     resetConnection(_wantReconnect);
292     // FIXME handle disconnects gracefully
293 }
294
295
296 // note: this still expects the legacy protocol
297 // noteĀ²: after cleaning this up, we can probably get rid of _socket altogether
298 void CoreConnection::coreHasData(const QVariant &item)
299 {
300     QVariantMap msg = item.toMap();
301     if (!msg.contains("MsgType")) {
302         // This core is way too old and does not even speak our init protocol...
303         emit connectionErrorPopup(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
304         disconnectFromCore(QString(), false);
305         return;
306     }
307     if (msg["MsgType"] == "ClientInitAck") {
308         clientInitAck(msg);
309     }
310     else if (msg["MsgType"] == "ClientInitReject") {
311         emit connectionErrorPopup(msg["Error"].toString());
312         disconnectFromCore(QString(), false);
313         return;
314     }
315     else if (msg["MsgType"] == "CoreSetupAck") {
316         emit coreSetupSuccess();
317     }
318     else if (msg["MsgType"] == "CoreSetupReject") {
319         emit coreSetupFailed(msg["Error"].toString());
320     }
321     else if (msg["MsgType"] == "ClientLoginReject") {
322         loginFailed(msg["Error"].toString());
323     }
324     else if (msg["MsgType"] == "ClientLoginAck") {
325         loginSuccess();
326     }
327     else if (msg["MsgType"] == "SessionInit") {
328         // that's it, let's hand over to the signal proxy
329         // if the connection is an orphan, the signalProxy adopts it.
330         // -> we don't need to care about it anymore
331
332         disconnect(_peer, 0, this, 0);
333
334         _peer->setParent(0);
335         Client::signalProxy()->addPeer(_peer);
336
337         sessionStateReceived(msg["SessionState"].toMap());
338     }
339     else {
340         disconnectFromCore(tr("Invalid data received from core"), false);
341         return;
342     }
343 }
344
345
346 void CoreConnection::disconnectFromCore()
347 {
348     if (_socket)
349         disconnectFromCore(QString(), false); // requested disconnect, so don't try to reconnect
350 }
351
352
353 void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect)
354 {
355     if (!wantReconnect)
356         _reconnectTimer.stop();
357
358     _wantReconnect = wantReconnect; // store if disconnect was requested
359     _wasReconnect = false;
360
361     if (_socket)
362         _socket->close();
363
364     if (errorString.isEmpty())
365         emit connectionError(tr("Disconnected"));
366     else
367         emit connectionError(errorString);
368 }
369
370
371 void CoreConnection::resetConnection(bool wantReconnect)
372 {
373     if (_resetting)
374         return;
375     _resetting = true;
376
377     _wantReconnect = wantReconnect;
378
379     if (_peer) {
380         disconnect(_socket, 0, this, 0);
381         disconnect(_peer, 0, this, 0);
382         _peer->close();
383
384         if (_peer->parent() == this)
385             _peer->deleteLater(); // if it's not us, it belongs to the sigproxy which will delete it
386         _socket = 0;      // socket is owned and will be deleted by RemoteConnection
387         _peer = 0;
388     }
389     else if (_socket) {
390         disconnect(_socket, 0, this, 0);
391         _socket->deleteLater();
392         _socket = 0;
393     }
394
395     _coreMsgBuffer.clear();
396     _netsToSync.clear();
397     _numNetsToSync = 0;
398
399     setProgressMaximum(-1); // disable
400     setState(Disconnected);
401     emit lagUpdated(-1);
402
403     emit connectionMsg(tr("Disconnected from core."));
404     emit encrypted(false);
405
406     // initiate if a reconnect if appropriate
407     CoreConnectionSettings s;
408     if (wantReconnect && s.autoReconnect()) {
409         _reconnectTimer.start();
410     }
411
412     _resetting = false;
413 }
414
415
416 void CoreConnection::reconnectToCore()
417 {
418     if (currentAccount().isValid()) {
419         _wasReconnect = true;
420         connectToCore(currentAccount().accountId());
421     }
422 }
423
424
425 bool CoreConnection::connectToCore(AccountId accId)
426 {
427     if (isConnected())
428         return false;
429
430     CoreAccountSettings s;
431
432     // FIXME: Don't force connection to internal core in mono client
433     if (Quassel::runMode() == Quassel::Monolithic) {
434         _account = accountModel()->account(accountModel()->internalAccount());
435         Q_ASSERT(_account.isValid());
436     }
437     else {
438         if (!accId.isValid()) {
439             // check our settings and figure out what to do
440             if (!s.autoConnectOnStartup())
441                 return false;
442             if (s.autoConnectToFixedAccount())
443                 accId = s.autoConnectAccount();
444             else
445                 accId = s.lastAccount();
446             if (!accId.isValid())
447                 return false;
448         }
449         _account = accountModel()->account(accId);
450         if (!_account.accountId().isValid()) {
451             return false;
452         }
453         if (Quassel::runMode() != Quassel::Monolithic) {
454             if (_account.isInternal())
455                 return false;
456         }
457     }
458
459     s.setLastAccount(accId);
460     connectToCurrentAccount();
461     return true;
462 }
463
464
465 void CoreConnection::connectToCurrentAccount()
466 {
467     resetConnection(false);
468
469     if (currentAccount().isInternal()) {
470         if (Quassel::runMode() != Quassel::Monolithic) {
471             qWarning() << "Cannot connect to internal core in client-only mode!";
472             return;
473         }
474         emit startInternalCore();
475
476         InternalPeer *peer = new InternalPeer();
477         Client::instance()->signalProxy()->addPeer(peer); // sigproxy will take ownership
478         emit connectToInternalCore(peer);
479
480         return;
481     }
482
483     CoreAccountSettings s;
484
485     Q_ASSERT(!_socket);
486 #ifdef HAVE_SSL
487     QSslSocket *sock = new QSslSocket(this);
488     // make sure the warning is shown if we happen to connect without SSL support later
489     s.setAccountValue("ShowNoClientSslWarning", true);
490 #else
491     if (_account.useSsl()) {
492         if (s.accountValue("ShowNoClientSslWarning", true).toBool()) {
493             bool accepted = false;
494             emit handleNoSslInClient(&accepted);
495             if (!accepted) {
496                 emit connectionError(tr("Unencrypted connection canceled"));
497                 return;
498             }
499             s.setAccountValue("ShowNoClientSslWarning", false);
500         }
501     }
502     QTcpSocket *sock = new QTcpSocket(this);
503 #endif
504
505 #ifndef QT_NO_NETWORKPROXY
506     if (_account.useProxy()) {
507         QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
508         sock->setProxy(proxy);
509     }
510 #endif
511
512     _socket = sock;
513     connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected()));
514     connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
515     connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError)));
516     connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
517
518     emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName()));
519     sock->connectToHost(_account.hostName(), _account.port());
520 }
521
522
523 void CoreConnection::coreSocketConnected()
524 {
525     // Create the connection which will handle the incoming data
526     Q_ASSERT(!_peer);
527     _peer = new LegacyPeer(_socket, this);
528     connect(_peer, SIGNAL(dataReceived(QVariant)), SLOT(coreHasData(QVariant)));
529     connect(_peer, SIGNAL(transferProgress(int,int)), SLOT(updateProgress(int,int)));
530
531     // Phase One: Send client info and wait for core info
532
533     emit connectionMsg(tr("Synchronizing to core..."));
534
535     QVariantMap clientInit;
536     clientInit["MsgType"] = "ClientInit";
537     clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
538     clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
539     clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
540     clientInit["UseSsl"] = _account.useSsl();
541 #ifndef QT_NO_COMPRESS
542     clientInit["UseCompression"] = true;
543 #else
544     clientInit["UseCompression"] = false;
545 #endif
546
547     qobject_cast<RemotePeer *>(_peer)->writeSocketData(clientInit);
548 }
549
550
551 void CoreConnection::clientInitAck(const QVariantMap &msg)
552 {
553     // Core has accepted our version info and sent its own. Let's see if we accept it as well...
554     uint ver = msg["ProtocolVersion"].toUInt();
555     if (ver < Quassel::buildInfo().clientNeedsProtocol) {
556         emit connectionErrorPopup(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
557                                      "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
558         disconnectFromCore(QString(), false);
559         return;
560     }
561
562     Client::setCoreFeatures((Quassel::Features)msg["CoreFeatures"].toUInt());
563
564 #ifndef QT_NO_COMPRESS
565     if (msg["SupportsCompression"].toBool()) {
566         _socket->setProperty("UseCompression", true);
567     }
568 #endif
569
570     _coreMsgBuffer = msg;
571
572 #ifdef HAVE_SSL
573     CoreAccountSettings s;
574     if (currentAccount().useSsl()) {
575         if (msg["SupportSsl"].toBool()) {
576             // Make sure the warning is shown next time we don't have SSL in the core
577             s.setAccountValue("ShowNoCoreSslWarning", true);
578
579             QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
580             Q_ASSERT(sslSocket);
581             connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
582             connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
583             sslSocket->startClientEncryption();
584         }
585         else {
586             if (s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
587                 bool accepted = false;
588                 emit handleNoSslInCore(&accepted);
589                 if (!accepted) {
590                     disconnectFromCore(tr("Unencrypted connection canceled"), false);
591                     return;
592                 }
593                 s.setAccountValue("ShowNoCoreSslWarning", false);
594                 s.setAccountValue("SslCert", QString());
595             }
596             connectionReady();
597         }
598         return;
599     }
600 #endif
601     // if we use SSL we wait for the next step until every SSL warning has been cleared
602     connectionReady();
603 }
604
605
606 #ifdef HAVE_SSL
607
608 void CoreConnection::sslSocketEncrypted()
609 {
610     QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
611     Q_ASSERT(socket);
612
613     if (!socket->sslErrors().count()) {
614         // Cert is valid, so we don't want to store it as known
615         // That way, a warning will appear in case it becomes invalid at some point
616         CoreAccountSettings s;
617         s.setAccountValue("SSLCert", QString());
618     }
619
620     emit encrypted(true);
621     connectionReady();
622 }
623
624
625 void CoreConnection::sslErrors()
626 {
627     QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
628     Q_ASSERT(socket);
629
630     CoreAccountSettings s;
631     QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
632
633     if (knownDigest != socket->peerCertificate().digest()) {
634         bool accepted = false;
635         bool permanently = false;
636         emit handleSslErrors(socket, &accepted, &permanently);
637
638         if (!accepted) {
639             disconnectFromCore(tr("Unencrypted connection canceled"), false);
640             return;
641         }
642
643         if (permanently)
644             s.setAccountValue("SslCert", socket->peerCertificate().digest());
645         else
646             s.setAccountValue("SslCert", QString());
647     }
648
649     socket->ignoreSslErrors();
650 }
651
652
653 #endif /* HAVE_SSL */
654
655 void CoreConnection::connectionReady()
656 {
657     setState(Connected);
658     emit connectionMsg(tr("Connected to %1").arg(currentAccount().accountName()));
659
660     if (!_coreMsgBuffer["Configured"].toBool()) {
661         // start wizard
662         emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
663     }
664     else if (_coreMsgBuffer["LoginEnabled"].toBool()) {
665         loginToCore();
666     }
667     _coreMsgBuffer.clear();
668 }
669
670
671 void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember)
672 {
673     _account.setUser(user);
674     _account.setPassword(password);
675     _account.setStorePassword(remember);
676     loginToCore();
677 }
678
679
680 void CoreConnection::loginToCore(const QString &prevError)
681 {
682     emit connectionMsg(tr("Logging in..."));
683     if (currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
684         bool valid = false;
685         emit userAuthenticationRequired(&_account, &valid, prevError); // *must* be a synchronous call
686         if (!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
687             disconnectFromCore(tr("Login canceled"), false);
688             return;
689         }
690     }
691
692     QVariantMap clientLogin;
693     clientLogin["MsgType"] = "ClientLogin";
694     clientLogin["User"] = currentAccount().user();
695     clientLogin["Password"] = currentAccount().password();
696     qobject_cast<RemotePeer*>(_peer)->writeSocketData(clientLogin);
697 }
698
699
700 void CoreConnection::loginFailed(const QString &error)
701 {
702     loginToCore(error);
703 }
704
705
706 void CoreConnection::loginSuccess()
707 {
708     updateProgress(0, 0);
709
710     // save current account data
711     _model->createOrUpdateAccount(currentAccount());
712     _model->save();
713
714     _reconnectTimer.stop();
715
716     setProgressText(tr("Receiving session state"));
717     setState(Synchronizing);
718     emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
719 }
720
721
722 void CoreConnection::sessionStateReceived(const QVariantMap &state)
723 {
724     updateProgress(100, 100);
725
726     syncToCore(state);
727 }
728
729
730 void CoreConnection::internalSessionStateReceived(const QVariant &packedState)
731 {
732     updateProgress(100, 100);
733
734     setState(Synchronizing);
735     syncToCore(packedState.toMap());
736 }
737
738
739 void CoreConnection::syncToCore(const QVariantMap &sessionState)
740 {
741     if (sessionState.contains("CoreFeatures"))
742         Client::setCoreFeatures((Quassel::Features)sessionState["CoreFeatures"].toUInt());
743
744     setProgressText(tr("Receiving network states"));
745     updateProgress(0, 100);
746
747     // create identities
748     foreach(QVariant vid, sessionState["Identities"].toList()) {
749         Client::instance()->coreIdentityCreated(vid.value<Identity>());
750     }
751
752     // create buffers
753     // FIXME: get rid of this crap -- why?
754     QVariantList bufferinfos = sessionState["BufferInfos"].toList();
755     NetworkModel *networkModel = Client::networkModel();
756     Q_ASSERT(networkModel);
757     foreach(QVariant vinfo, bufferinfos)
758     networkModel->bufferUpdated(vinfo.value<BufferInfo>());  // create BufferItems
759
760     QVariantList networkids = sessionState["NetworkIds"].toList();
761
762     // prepare sync progress thingys...
763     // FIXME: Care about removal of networks
764     _numNetsToSync = networkids.count();
765     updateProgress(0, _numNetsToSync);
766
767     // create network objects
768     foreach(QVariant networkid, networkids) {
769         NetworkId netid = networkid.value<NetworkId>();
770         if (Client::network(netid))
771             continue;
772         Network *net = new Network(netid, Client::instance());
773         _netsToSync.insert(net);
774         connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
775         connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
776         Client::addNetwork(net);
777     }
778     checkSyncState();
779 }
780
781
782 // this is also called for destroyed networks!
783 void CoreConnection::networkInitDone()
784 {
785     QObject *net = sender();
786     Q_ASSERT(net);
787     disconnect(net, 0, this, 0);
788     _netsToSync.remove(net);
789     updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
790     checkSyncState();
791 }
792
793
794 void CoreConnection::checkSyncState()
795 {
796     if (_netsToSync.isEmpty() && state() >= Synchronizing) {
797         setState(Synchronized);
798         setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
799         setProgressMaximum(-1);
800         emit synchronized();
801     }
802 }
803
804
805 void CoreConnection::doCoreSetup(const QVariant &setupData)
806 {
807     QVariantMap setup;
808     setup["MsgType"] = "CoreSetupData";
809     setup["SetupData"] = setupData;
810     qobject_cast<RemotePeer *>(_peer)->writeSocketData(setup);
811 }