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