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