Actually make the client/core connection settings do something
[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::resetConnection(bool wantReconnect) {
106   _wantReconnect = wantReconnect;
107
108   if(_socket) {
109     disconnect(_socket, 0, this, 0);
110     _socket->deleteLater();
111     _socket = 0;
112   }
113   _blockSize = 0;
114
115   _coreMsgBuffer.clear();
116
117   _netsToSync.clear();
118   _numNetsToSync = 0;
119
120   setProgressMaximum(-1); // disable
121   setState(Disconnected);
122
123   emit connectionMsg(tr("Disconnected from core."));
124   emit encrypted(false);
125
126   // initiate if a reconnect if appropriate
127   CoreConnectionSettings s;
128   if(wantReconnect && s.autoReconnect()) {
129     _reconnectTimer.start();
130     //reconnectToCore();
131   }
132 }
133
134 void CoreConnection::reconnectTimeout() {
135   if(!_socket) {
136     CoreConnectionSettings s;
137     if(_wantReconnect && s.autoReconnect()) {
138
139 #ifdef HAVE_KDE
140       // If using Solid, we don't want to reconnect if we're offline
141       if(s.networkDetectionMode() == CoreConnectionSettings::UseSolid) {
142         if(Solid::Networking::status() != Solid::Networking::Connected
143            && Solid::Networking::status() != Solid::Networking::Unknown) {
144           return;
145         }
146       }
147 #endif /* HAVE_KDE */
148
149       reconnectToCore();
150     }
151   }
152 }
153
154 void CoreConnection::networkDetectionModeChanged(const QVariant &vmode) {
155   CoreConnectionSettings s;
156   CoreConnectionSettings::NetworkDetectionMode mode = (CoreConnectionSettings::NetworkDetectionMode)vmode.toInt();
157   if(mode == CoreConnectionSettings::UsePingTimeout)
158     Client::signalProxy()->setMaxHeartBeatCount(s.pingTimeoutInterval() / 30);
159   else {
160     Client::signalProxy()->setMaxHeartBeatCount(-1);
161   }
162 }
163
164 void CoreConnection::pingTimeoutIntervalChanged(const QVariant &interval) {
165   CoreConnectionSettings s;
166   if(s.networkDetectionMode() == CoreConnectionSettings::UsePingTimeout)
167     Client::signalProxy()->setMaxHeartBeatCount(interval.toInt() / 30); // interval is 30 seconds
168 }
169
170 void CoreConnection::reconnectIntervalChanged(const QVariant &interval) {
171   _reconnectTimer.setInterval(interval.toInt() * 1000);
172 }
173
174 #ifdef HAVE_KDE
175
176 void CoreConnection::solidNetworkStatusChanged(Solid::Networking::Status status) {
177   CoreConnectionSettings s;
178   if(s.networkDetectionMode() != CoreConnectionSettings::UseSolid)
179     return;
180
181   switch(status) {
182   case Solid::Networking::Unknown:
183   case Solid::Networking::Connected:
184     qDebug() << "Solid: Network status changed to connected or unknown";
185     if(state() == Disconnected) {
186       if(_wantReconnect && s.autoReconnect()) {
187         reconnectToCore();
188       }
189     }
190     break;
191   case Solid::Networking::Unconnected:
192     qDebug() << "Solid: Disconnected";
193     if(!isLocalConnection())
194       disconnectFromCore(tr("Network is down"), true);
195     break;
196   default:
197     break;
198   }
199 }
200
201 #endif
202
203 bool CoreConnection::isEncrypted() const {
204 #ifndef HAVE_SSL
205   return false;
206 #else
207   QSslSocket *sock = qobject_cast<QSslSocket *>(_socket);
208   return isConnected() && sock && sock->isEncrypted();
209 #endif
210 }
211
212 bool CoreConnection::isLocalConnection() const {
213   if(!isConnected())
214     return false;
215   if(currentAccount().isInternal())
216     return true;
217   if(_socket->peerAddress().isInSubnet(QHostAddress::LocalHost, 0x00ffffff))
218     return true;
219
220   return false;
221 }
222
223 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
224   QString text;
225
226   switch(socketState) {
227   case QAbstractSocket::UnconnectedState:
228     text = tr("Disconnected");
229     break;
230   case QAbstractSocket::HostLookupState:
231     text = tr("Looking up %1...").arg(currentAccount().hostName());
232     break;
233   case QAbstractSocket::ConnectingState:
234     text = tr("Connecting to %1...").arg(currentAccount().hostName());
235     break;
236   case QAbstractSocket::ConnectedState:
237     text = tr("Connected to %1").arg(currentAccount().hostName());
238     break;
239   case QAbstractSocket::ClosingState:
240     text = tr("Disconnecting from %1...").arg(currentAccount().hostName());
241     break;
242   default:
243     break;
244   }
245
246   if(!text.isEmpty())
247     emit progressTextChanged(text);
248
249   setState(socketState);
250 }
251
252 void CoreConnection::setState(QAbstractSocket::SocketState socketState) {
253   ConnectionState state;
254
255   switch(socketState) {
256   case QAbstractSocket::UnconnectedState:
257     state = Disconnected;
258     break;
259   case QAbstractSocket::HostLookupState:
260   case QAbstractSocket::ConnectingState:
261   case QAbstractSocket::ConnectedState:  // we'll set it to Connected in connectionReady()
262     state = Connecting;
263     break;
264   default:
265     state = Disconnected;
266   }
267
268   setState(state);
269 }
270
271 void CoreConnection::setState(ConnectionState state) {
272   if(state != _state) {
273     _state = state;
274     emit stateChanged(state);
275     if(state == Disconnected)
276       emit disconnected();
277   }
278 }
279
280 void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
281   qDebug() << "coreSocketError" << _socket << _socket->errorString();
282   disconnectFromCore(_socket->errorString(), true);
283 }
284
285 void CoreConnection::coreSocketDisconnected() {
286   emit disconnected();
287   qDebug() << Q_FUNC_INFO;
288   resetConnection(true);
289   // FIXME handle disconnects gracefully
290 }
291
292 void CoreConnection::coreHasData() {
293   QVariant item;
294   while(SignalProxy::readDataFromDevice(_socket, _blockSize, item)) {
295     QVariantMap msg = item.toMap();
296     if(!msg.contains("MsgType")) {
297       // This core is way too old and does not even speak our init protocol...
298       emit connectionErrorPopup(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
299       disconnectFromCore(QString(), false);
300       return;
301     }
302     if(msg["MsgType"] == "ClientInitAck") {
303       clientInitAck(msg);
304     } else if(msg["MsgType"] == "ClientInitReject") {
305       emit connectionErrorPopup(msg["Error"].toString());
306       disconnectFromCore(QString(), false);
307       return;
308     } else if(msg["MsgType"] == "CoreSetupAck") {
309       emit coreSetupSuccess();
310     } else if(msg["MsgType"] == "CoreSetupReject") {
311       emit coreSetupFailed(msg["Error"].toString());
312     } else if(msg["MsgType"] == "ClientLoginReject") {
313       loginFailed(msg["Error"].toString());
314     } else if(msg["MsgType"] == "ClientLoginAck") {
315       loginSuccess();
316     } else if(msg["MsgType"] == "SessionInit") {
317       // that's it, let's hand over to the signal proxy
318       // if the socket is an orphan, the signalProxy adopts it.
319       // -> we don't need to care about it anymore
320       _socket->setParent(0);
321       Client::signalProxy()->addPeer(_socket);
322
323       sessionStateReceived(msg["SessionState"].toMap());
324       break; // this is definitively the last message we process here!
325     } else {
326       disconnectFromCore(tr("Invalid data received from core"), false);
327       return;
328     }
329   }
330   if(_blockSize > 0) {
331     updateProgress(_socket->bytesAvailable(), _blockSize);
332   }
333 }
334
335 void CoreConnection::disconnectFromCore() {
336   disconnectFromCore(QString(), false);  // requested disconnect, so don't try to reconnect
337 }
338
339 void CoreConnection::disconnectFromCore(const QString &errorString, bool wantReconnect) {
340   if(!wantReconnect)
341     _reconnectTimer.stop();
342
343   if(errorString.isEmpty())
344     emit connectionError(tr("Disconnected"));
345   else
346     emit connectionError(errorString);
347
348   Client::signalProxy()->removeAllPeers();
349   resetConnection(wantReconnect);
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 #ifndef QT_NO_COMPRESS
478   if(msg["SupportsCompression"].toBool()) {
479     _socket->setProperty("UseCompression", true);
480   }
481 #endif
482
483   _coreMsgBuffer = msg;
484
485 #ifdef HAVE_SSL
486   CoreAccountSettings s;
487   if(currentAccount().useSsl()) {
488     if(msg["SupportSsl"].toBool()) {
489       // Make sure the warning is shown next time we don't have SSL in the core
490       s.setAccountValue("ShowNoCoreSslWarning", true);
491
492       QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
493       Q_ASSERT(sslSocket);
494       connect(sslSocket, SIGNAL(encrypted()), SLOT(sslSocketEncrypted()));
495       connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors()));
496       sslSocket->startClientEncryption();
497     } else {
498       if(s.accountValue("ShowNoCoreSslWarning", true).toBool()) {
499         bool accepted = false;
500         emit handleNoSslInCore(&accepted);
501         if(!accepted) {
502           disconnectFromCore(tr("Unencrypted connection canceled"), false);
503           return;
504         }
505         s.setAccountValue("ShowNoCoreSslWarning", false);
506         s.setAccountValue("SslCert", QString());
507       }
508       connectionReady();
509     }
510     return;
511   }
512 #endif
513   // if we use SSL we wait for the next step until every SSL warning has been cleared
514   connectionReady();
515 }
516
517 #ifdef HAVE_SSL
518
519 void CoreConnection::sslSocketEncrypted() {
520   QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
521   Q_ASSERT(socket);
522
523   if(!socket->sslErrors().count()) {
524     // Cert is valid, so we don't want to store it as known
525     // That way, a warning will appear in case it becomes invalid at some point
526     CoreAccountSettings s;
527     s.setAccountValue("SSLCert", QString());
528   }
529
530   emit encrypted(true);
531   connectionReady();
532 }
533
534 void CoreConnection::sslErrors() {
535   QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
536   Q_ASSERT(socket);
537
538   CoreAccountSettings s;
539   QByteArray knownDigest = s.accountValue("SslCert").toByteArray();
540
541   if(knownDigest != socket->peerCertificate().digest()) {
542     bool accepted = false;
543     bool permanently = false;
544     emit handleSslErrors(socket, &accepted, &permanently);
545
546     if(!accepted) {
547       disconnectFromCore(tr("Unencrypted connection canceled"), false);
548       return;
549     }
550
551     if(permanently)
552       s.setAccountValue("SslCert", socket->peerCertificate().digest());
553     else
554       s.setAccountValue("SslCert", QString());
555   }
556
557   socket->ignoreSslErrors();
558 }
559
560 #endif /* HAVE_SSL */
561
562 void CoreConnection::connectionReady() {
563   setState(Connected);
564   emit connectionMsg(tr("Connected to %1").arg(currentAccount().accountName()));
565
566   if(!_coreMsgBuffer["Configured"].toBool()) {
567     // start wizard
568     emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
569   } else if(_coreMsgBuffer["LoginEnabled"].toBool()) {
570     loginToCore();
571   }
572   _coreMsgBuffer.clear();
573 }
574
575 void CoreConnection::loginToCore(const QString &user, const QString &password, bool remember) {
576   _account.setUser(user);
577   _account.setPassword(password);
578   _account.setStorePassword(remember);
579   loginToCore();
580 }
581
582 void CoreConnection::loginToCore(const QString &prevError) {
583   emit connectionMsg(tr("Logging in..."));
584   if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
585     bool valid = false;
586     emit userAuthenticationRequired(&_account, &valid, prevError);  // *must* be a synchronous call
587     if(!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
588       disconnectFromCore(tr("Login canceled"), false);
589       return;
590     }
591   }
592
593   QVariantMap clientLogin;
594   clientLogin["MsgType"] = "ClientLogin";
595   clientLogin["User"] = currentAccount().user();
596   clientLogin["Password"] = currentAccount().password();
597   SignalProxy::writeDataToDevice(_socket, clientLogin);
598 }
599
600 void CoreConnection::loginFailed(const QString &error) {
601   loginToCore(error);
602 }
603
604 void CoreConnection::loginSuccess() {
605   updateProgress(0, 0);
606
607   // save current account data
608   _model->createOrUpdateAccount(currentAccount());
609   _model->save();
610
611   _reconnectTimer.stop();
612
613   setProgressText(tr("Receiving session state"));
614   setState(Synchronizing);
615   emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
616 }
617
618 void CoreConnection::sessionStateReceived(const QVariantMap &state) {
619   updateProgress(100, 100);
620
621   // rest of communication happens through SignalProxy...
622   disconnect(_socket, SIGNAL(readyRead()), this, 0);
623   disconnect(_socket, SIGNAL(connected()), this, 0);
624
625   syncToCore(state);
626 }
627
628 void CoreConnection::internalSessionStateReceived(const QVariant &packedState) {
629   updateProgress(100, 100);
630
631   setState(Synchronizing);
632   syncToCore(packedState.toMap());
633 }
634
635 void CoreConnection::syncToCore(const QVariantMap &sessionState) {
636   setProgressText(tr("Receiving network states"));
637   updateProgress(0, 100);
638
639   // create identities
640   foreach(QVariant vid, sessionState["Identities"].toList()) {
641     Client::instance()->coreIdentityCreated(vid.value<Identity>());
642   }
643
644   // create buffers
645   // FIXME: get rid of this crap -- why?
646   QVariantList bufferinfos = sessionState["BufferInfos"].toList();
647   NetworkModel *networkModel = Client::networkModel();
648   Q_ASSERT(networkModel);
649   foreach(QVariant vinfo, bufferinfos)
650     networkModel->bufferUpdated(vinfo.value<BufferInfo>());  // create BufferItems
651
652   QVariantList networkids = sessionState["NetworkIds"].toList();
653
654   // prepare sync progress thingys...
655   // FIXME: Care about removal of networks
656   _numNetsToSync = networkids.count();
657   updateProgress(0, _numNetsToSync);
658
659   // create network objects
660   foreach(QVariant networkid, networkids) {
661     NetworkId netid = networkid.value<NetworkId>();
662     if(Client::network(netid))
663       continue;
664     Network *net = new Network(netid, Client::instance());
665     _netsToSync.insert(net);
666     connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
667     connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
668     Client::addNetwork(net);
669   }
670   checkSyncState();
671 }
672
673 void CoreConnection::networkInitDone() {
674   Network *net = qobject_cast<Network *>(sender());
675   Q_ASSERT(net);
676   disconnect(net, 0, this, 0);
677   _netsToSync.remove(net);
678   updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
679   checkSyncState();
680 }
681
682 void CoreConnection::checkSyncState() {
683   if(_netsToSync.isEmpty()) {
684     setState(Synchronized);
685     setProgressText(tr("Synchronized to %1").arg(currentAccount().accountName()));
686     setProgressMaximum(-1);
687     emit synchronized();
688   }
689 }
690
691 void CoreConnection::doCoreSetup(const QVariant &setupData) {
692   QVariantMap setup;
693   setup["MsgType"] = "CoreSetupData";
694   setup["SetupData"] = setupData;
695   SignalProxy::writeDataToDevice(_socket, setup);
696 }