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