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