1 /***************************************************************************
2 * Copyright (C) 2009 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include "coreconnection.h"
23 #ifndef QT_NO_NETWORKPROXY
24 # include <QNetworkProxy>
28 #include "clientsettings.h"
29 #include "coreaccountmodel.h"
32 #include "networkmodel.h"
34 #include "signalproxy.h"
37 CoreConnection::CoreConnection(CoreAccountModel *model, QObject *parent)
46 qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
50 void CoreConnection::init() {
51 connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
54 void CoreConnection::setProgressText(const QString &text) {
55 if(_progressText != text) {
57 emit progressTextChanged(text);
61 void CoreConnection::setProgressValue(int value) {
62 if(_progressValue != value) {
63 _progressValue = value;
64 emit progressValueChanged(value);
68 void CoreConnection::setProgressMinimum(int minimum) {
69 if(_progressMinimum != minimum) {
70 _progressMinimum = minimum;
71 emit progressRangeChanged(minimum, _progressMaximum);
75 void CoreConnection::setProgressMaximum(int maximum) {
76 if(_progressMaximum != maximum) {
77 _progressMaximum = maximum;
78 emit progressRangeChanged(_progressMinimum, maximum);
82 void CoreConnection::updateProgress(int value, int max) {
83 if(max != _progressMaximum) {
84 _progressMaximum = max;
85 emit progressRangeChanged(_progressMinimum, _progressMaximum);
87 setProgressValue(value);
90 void CoreConnection::resetConnection() {
92 disconnect(_socket, 0, this, 0);
93 _socket->deleteLater();
98 _coreMsgBuffer.clear();
103 setProgressMaximum(-1); // disable
104 setState(Disconnected);
105 emit connectionMsg(tr("Disconnected from core."));
108 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
111 switch(socketState) {
112 case QAbstractSocket::UnconnectedState:
113 text = tr("Disconnected.");
115 case QAbstractSocket::HostLookupState:
116 text = tr("Looking up %1...").arg(currentAccount().hostName());
118 case QAbstractSocket::ConnectingState:
119 text = tr("Connecting to %1...").arg(currentAccount().hostName());
121 case QAbstractSocket::ConnectedState:
122 text = tr("Connected to %1.").arg(currentAccount().hostName());
124 case QAbstractSocket::ClosingState:
125 text = tr("Disconnecting from %1...").arg(currentAccount().hostName());
132 emit progressTextChanged(text);
134 setState(socketState);
137 void CoreConnection::setState(QAbstractSocket::SocketState socketState) {
138 ConnectionState state;
140 switch(socketState) {
141 case QAbstractSocket::UnconnectedState:
142 state = Disconnected;
144 case QAbstractSocket::HostLookupState:
145 case QAbstractSocket::ConnectingState:
148 case QAbstractSocket::ConnectedState:
152 state = Disconnected;
158 void CoreConnection::setState(ConnectionState state) {
159 if(state != _state) {
161 emit stateChanged(state);
162 if(state == Disconnected)
167 void CoreConnection::setWarningsHandler(const char *slot) {
168 resetWarningsHandler();
169 connect(this, SIGNAL(handleIgnoreWarnings(bool)), this, slot);
172 void CoreConnection::resetWarningsHandler() {
173 disconnect(this, SIGNAL(handleIgnoreWarnings(bool)), this, 0);
176 void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
177 qDebug() << "coreSocketError" << _socket << _socket->errorString();
178 emit connectionError(_socket->errorString());
182 void CoreConnection::coreSocketDisconnected() {
185 // FIXME handle disconnects gracefully
188 void CoreConnection::coreHasData() {
190 while(SignalProxy::readDataFromDevice(_socket, _blockSize, item)) {
191 QVariantMap msg = item.toMap();
192 if(!msg.contains("MsgType")) {
193 // This core is way too old and does not even speak our init protocol...
194 emit connectionError(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
195 disconnectFromCore();
198 if(msg["MsgType"] == "ClientInitAck") {
200 } else if(msg["MsgType"] == "ClientInitReject") {
201 emit connectionError(msg["Error"].toString());
202 disconnectFromCore();
204 } else if(msg["MsgType"] == "CoreSetupAck") {
205 //emit coreSetupSuccess();
206 } else if(msg["MsgType"] == "CoreSetupReject") {
207 //emit coreSetupFailed(msg["Error"].toString());
208 } else if(msg["MsgType"] == "ClientLoginReject") {
209 loginFailed(msg["Error"].toString());
210 } else if(msg["MsgType"] == "ClientLoginAck") {
212 } else if(msg["MsgType"] == "SessionInit") {
213 // that's it, let's hand over to the signal proxy
214 // if the socket is an orphan, the signalProxy adopts it.
215 // -> we don't need to care about it anymore
216 _socket->setParent(0);
217 Client::signalProxy()->addPeer(_socket);
219 sessionStateReceived(msg["SessionState"].toMap());
220 break; // this is definitively the last message we process here!
222 emit connectionError(tr("<b>Invalid data received from core!</b><br>Disconnecting."));
223 disconnectFromCore();
228 updateProgress(_socket->bytesAvailable(), _blockSize);
232 void CoreConnection::disconnectFromCore() {
233 Client::signalProxy()->removeAllPeers();
237 void CoreConnection::reconnectToCore() {
238 if(currentAccount().isValid())
239 connectToCore(currentAccount().accountId());
242 bool CoreConnection::connectToCore(AccountId accId) {
246 CoreAccountSettings s;
248 // FIXME: Don't force connection to internal core in mono client
249 if(Quassel::runMode() == Quassel::Monolithic) {
250 _account = accountModel()->account(accountModel()->internalAccount());
251 Q_ASSERT(_account.isValid());
253 if(!accId.isValid()) {
254 // check our settings and figure out what to do
255 if(!s.autoConnectOnStartup())
257 if(s.autoConnectToFixedAccount())
258 accId = s.autoConnectAccount();
260 accId = s.lastAccount();
264 _account = accountModel()->account(accId);
265 if(!_account.accountId().isValid()) {
268 if(Quassel::runMode() != Quassel::Monolithic) {
269 if(_account.isInternal())
274 s.setLastAccount(accId);
275 connectToCurrentAccount();
279 void CoreConnection::connectToCurrentAccount() {
282 if(currentAccount().isInternal()) {
283 if(Quassel::runMode() != Quassel::Monolithic) {
284 qWarning() << "Cannot connect to internal core in client-only mode!";
287 emit startInternalCore();
288 emit connectToInternalCore(Client::instance()->signalProxy());
294 QSslSocket *sock = new QSslSocket(Client::instance());
296 if(_account.useSsl()) {
297 emit connectionError(tr("<b>This client is built without SSL Support!</b><br />Disable the usage of SSL in the account settings."));
300 QTcpSocket *sock = new QTcpSocket(Client::instance());
303 #ifndef QT_NO_NETWORKPROXY
304 if(_account.useProxy()) {
305 QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
306 sock->setProxy(proxy);
311 connect(sock, SIGNAL(readyRead()), SLOT(coreHasData()));
312 connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected()));
313 connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
314 connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError)));
315 connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
317 emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName()));
318 sock->connectToHost(_account.hostName(), _account.port());
321 void CoreConnection::coreSocketConnected() {
322 // Phase One: Send client info and wait for core info
324 emit connectionMsg(tr("Synchronizing to core..."));
326 QVariantMap clientInit;
327 clientInit["MsgType"] = "ClientInit";
328 clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
329 clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
330 clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
331 clientInit["UseSsl"] = _account.useSsl();
332 #ifndef QT_NO_COMPRESS
333 clientInit["UseCompression"] = true;
335 clientInit["UseCompression"] = false;
338 SignalProxy::writeDataToDevice(_socket, clientInit);
341 void CoreConnection::clientInitAck(const QVariantMap &msg) {
342 // Core has accepted our version info and sent its own. Let's see if we accept it as well...
343 uint ver = msg["ProtocolVersion"].toUInt();
344 if(ver < Quassel::buildInfo().clientNeedsProtocol) {
345 emit connectionError(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
346 "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
347 disconnectFromCore();
351 #ifndef QT_NO_COMPRESS
352 if(msg["SupportsCompression"].toBool()) {
353 _socket->setProperty("UseCompression", true);
357 _coreMsgBuffer = msg;
359 if(currentAccount().useSsl()) {
360 if(msg["SupportSsl"].toBool()) {
361 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
363 connect(sslSocket, SIGNAL(encrypted()), this, SLOT(sslSocketEncrypted()));
364 connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
366 sslSocket->startClientEncryption();
368 emit connectionError(tr("<b>The Quassel Core you are trying to connect to does not support SSL!</b><br />If you want to connect anyways, disable the usage of SSL in the account settings."));
369 disconnectFromCore();
374 // if we use SSL we wait for the next step until every SSL warning has been cleared
379 void CoreConnection::connectionReady() {
380 if(!_coreMsgBuffer["Configured"].toBool()) {
382 emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
383 } else if(_coreMsgBuffer["LoginEnabled"].toBool()) {
386 _coreMsgBuffer.clear();
387 resetWarningsHandler();
390 void CoreConnection::loginToCore(const QString &prevError) {
391 emit connectionMsg(tr("Logging in..."));
392 if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty() || !prevError.isEmpty()) {
394 emit userAuthenticationRequired(&_account, &valid, prevError); // *must* be a synchronous call
395 if(!valid || currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
396 disconnectFromCore();
397 emit connectionError(tr("Login canceled"));
402 QVariantMap clientLogin;
403 clientLogin["MsgType"] = "ClientLogin";
404 clientLogin["User"] = currentAccount().user();
405 clientLogin["Password"] = currentAccount().password();
406 SignalProxy::writeDataToDevice(_socket, clientLogin);
409 void CoreConnection::loginFailed(const QString &error) {
413 void CoreConnection::loginSuccess() {
414 updateProgress(0, 0);
416 // save current account data
417 _model->createOrUpdateAccount(currentAccount());
420 setProgressText(tr("Receiving session state"));
421 setState(Synchronizing);
422 emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
425 void CoreConnection::sessionStateReceived(const QVariantMap &state) {
426 updateProgress(100, 100);
428 // rest of communication happens through SignalProxy...
429 disconnect(_socket, SIGNAL(readyRead()), this, 0);
430 disconnect(_socket, SIGNAL(connected()), this, 0);
435 void CoreConnection::internalSessionStateReceived(const QVariant &packedState) {
436 updateProgress(100, 100);
438 setState(Synchronizing);
439 syncToCore(packedState.toMap());
442 void CoreConnection::syncToCore(const QVariantMap &sessionState) {
443 setProgressText(tr("Receiving network states"));
444 updateProgress(0, 100);
447 foreach(QVariant vid, sessionState["Identities"].toList()) {
448 Client::instance()->coreIdentityCreated(vid.value<Identity>());
452 // FIXME: get rid of this crap -- why?
453 QVariantList bufferinfos = sessionState["BufferInfos"].toList();
454 NetworkModel *networkModel = Client::networkModel();
455 Q_ASSERT(networkModel);
456 foreach(QVariant vinfo, bufferinfos)
457 networkModel->bufferUpdated(vinfo.value<BufferInfo>()); // create BufferItems
459 QVariantList networkids = sessionState["NetworkIds"].toList();
461 // prepare sync progress thingys...
462 // FIXME: Care about removal of networks
463 _numNetsToSync = networkids.count();
464 updateProgress(0, _numNetsToSync);
466 // create network objects
467 foreach(QVariant networkid, networkids) {
468 NetworkId netid = networkid.value<NetworkId>();
469 if(Client::network(netid))
471 Network *net = new Network(netid, Client::instance());
472 _netsToSync.insert(net);
473 connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
474 connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
475 Client::addNetwork(net);
480 void CoreConnection::networkInitDone() {
481 Network *net = qobject_cast<Network *>(sender());
483 disconnect(net, 0, this, 0);
484 _netsToSync.remove(net);
485 updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
489 void CoreConnection::checkSyncState() {
490 if(_netsToSync.isEmpty()) {
491 setState(Synchronized);
492 setProgressText(QString());
493 setProgressMaximum(-1);