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