Respect autoconnect settings in CoreConnection
[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   _progressMinimum(0),
42   _progressMaximum(-1),
43   _progressValue(-1)
44 {
45   qRegisterMetaType<ConnectionState>("CoreConnection::ConnectionState");
46
47 }
48
49 void CoreConnection::init() {
50   connect(Client::signalProxy(), SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
51 }
52
53 void CoreConnection::setProgressText(const QString &text) {
54   if(_progressText != text) {
55     _progressText = text;
56     emit progressTextChanged(text);
57   }
58 }
59
60 void CoreConnection::setProgressValue(int value) {
61   if(_progressValue != value) {
62     _progressValue = value;
63     emit progressValueChanged(value);
64   }
65 }
66
67 void CoreConnection::setProgressMinimum(int minimum) {
68   if(_progressMinimum != minimum) {
69     _progressMinimum = minimum;
70     emit progressRangeChanged(minimum, _progressMaximum);
71   }
72 }
73
74 void CoreConnection::setProgressMaximum(int maximum) {
75   if(_progressMaximum != maximum) {
76     _progressMaximum = maximum;
77     emit progressRangeChanged(_progressMinimum, maximum);
78   }
79 }
80
81 void CoreConnection::updateProgress(int value, int max) {
82   if(max != _progressMaximum) {
83     _progressMaximum = max;
84     emit progressRangeChanged(_progressMinimum, _progressMaximum);
85   }
86   setProgressValue(value);
87 }
88
89 void CoreConnection::resetConnection() {
90   if(_socket) {
91     disconnect(_socket, 0, this, 0);
92     _socket->deleteLater();
93     _socket = 0;
94   }
95   _blockSize = 0;
96
97   _coreMsgBuffer.clear();
98
99   _netsToSync.clear();
100   _numNetsToSync = 0;
101
102   setProgressMaximum(-1); // disable
103   emit connectionMsg(tr("Disconnected from core."));
104 }
105
106 void CoreConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
107   QString text;
108
109   switch(socketState) {
110   case QAbstractSocket::UnconnectedState:
111     text = tr("Disconnected.");
112     break;
113   case QAbstractSocket::HostLookupState:
114     text = tr("Looking up %1...").arg(currentAccount().hostName());
115     break;
116   case QAbstractSocket::ConnectingState:
117     text = tr("Connecting to %1...").arg(currentAccount().hostName());
118     break;
119   case QAbstractSocket::ConnectedState:
120     text = tr("Connected to %1.").arg(currentAccount().hostName());
121     break;
122   case QAbstractSocket::ClosingState:
123     text = tr("Disconnecting from %1...").arg(currentAccount().hostName());
124     break;
125   default:
126     break;
127   }
128
129   if(!text.isEmpty())
130     emit progressTextChanged(text);
131
132   setState(socketState);
133 }
134
135 void CoreConnection::setState(QAbstractSocket::SocketState socketState) {
136   ConnectionState state;
137
138   switch(socketState) {
139   case QAbstractSocket::UnconnectedState:
140     state = Disconnected;
141     break;
142   case QAbstractSocket::HostLookupState:
143   case QAbstractSocket::ConnectingState:
144     state = Connecting;
145     break;
146   case QAbstractSocket::ConnectedState:
147     state = Connected;
148     break;
149   default:
150     state = Disconnected;
151   }
152
153   setState(state);
154 }
155
156 void CoreConnection::setState(ConnectionState state) {
157   if(state != _state) {
158     _state = state;
159     emit stateChanged(state);
160   }
161 }
162
163 void CoreConnection::setWarningsHandler(const char *slot) {
164   resetWarningsHandler();
165   connect(this, SIGNAL(handleIgnoreWarnings(bool)), this, slot);
166 }
167
168 void CoreConnection::resetWarningsHandler() {
169   disconnect(this, SIGNAL(handleIgnoreWarnings(bool)), this, 0);
170 }
171
172 void CoreConnection::coreSocketError(QAbstractSocket::SocketError) {
173   qDebug() << "coreSocketError" << _socket << _socket->errorString();
174   emit connectionError(_socket->errorString());
175   resetConnection();
176 }
177
178 void CoreConnection::coreSocketDisconnected() {
179   setState(Disconnected);
180   emit disconnected();
181   resetConnection();
182   // FIXME handle disconnects gracefully
183 }
184
185 void CoreConnection::coreHasData() {
186   QVariant item;
187   while(SignalProxy::readDataFromDevice(_socket, _blockSize, item)) {
188     QVariantMap msg = item.toMap();
189     if(!msg.contains("MsgType")) {
190       // This core is way too old and does not even speak our init protocol...
191       emit connectionError(tr("The Quassel Core you try to connect to is too old! Please consider upgrading."));
192       disconnectFromCore();
193       return;
194     }
195     if(msg["MsgType"] == "ClientInitAck") {
196       clientInitAck(msg);
197     } else if(msg["MsgType"] == "ClientInitReject") {
198       emit connectionError(msg["Error"].toString());
199       disconnectFromCore();
200       return;
201     } else if(msg["MsgType"] == "CoreSetupAck") {
202       //emit coreSetupSuccess();
203     } else if(msg["MsgType"] == "CoreSetupReject") {
204       //emit coreSetupFailed(msg["Error"].toString());
205     } else if(msg["MsgType"] == "ClientLoginReject") {
206       loginFailed(msg["Error"].toString());
207     } else if(msg["MsgType"] == "ClientLoginAck") {
208       loginSuccess();
209     } else if(msg["MsgType"] == "SessionInit") {
210       // that's it, let's hand over to the signal proxy
211       // if the socket is an orphan, the signalProxy adopts it.
212       // -> we don't need to care about it anymore
213       _socket->setParent(0);
214       Client::signalProxy()->addPeer(_socket);
215
216       sessionStateReceived(msg["SessionState"].toMap());
217       break; // this is definitively the last message we process here!
218     } else {
219       emit connectionError(tr("<b>Invalid data received from core!</b><br>Disconnecting."));
220       disconnectFromCore();
221       return;
222     }
223   }
224   if(_blockSize > 0) {
225     updateProgress(_socket->bytesAvailable(), _blockSize);
226   }
227 }
228
229 void CoreConnection::disconnectFromCore() {
230   if(isConnected()) {
231     Client::signalProxy()->removeAllPeers();
232     resetConnection();
233   }
234 }
235
236 void CoreConnection::reconnectToCore() {
237   if(currentAccount().isValid())
238     connectToCore(currentAccount().accountId());
239 }
240
241 bool CoreConnection::connectToCore(AccountId accId) {
242   CoreAccountSettings s;
243
244   if(!accId.isValid()) {
245     // check our settings and figure out what to do
246     if(!s.autoConnectOnStartup())
247       return false;
248     if(s.autoConnectToFixedAccount())
249       accId = s.autoConnectAccount();
250     else
251       accId = s.lastAccount();
252     if(!accId.isValid())
253       return false;
254   }
255   _account = accountModel()->account(accId);
256   if(!_account.accountId().isValid()) {
257     return false;
258   }
259
260   s.setLastAccount(accId);
261   connectToCurrentAccount();
262   return true;
263 }
264
265 void CoreConnection::connectToCurrentAccount() {
266   resetConnection();
267
268   Q_ASSERT(!_socket);
269 #ifdef HAVE_SSL
270   QSslSocket *sock = new QSslSocket(Client::instance());
271 #else
272   if(_account.useSsl()) {
273     emit connectionError(tr("<b>This client is built without SSL Support!</b><br />Disable the usage of SSL in the account settings."));
274     return;
275   }
276   QTcpSocket *sock = new QTcpSocket(Client::instance());
277 #endif
278
279 #ifndef QT_NO_NETWORKPROXY
280   if(_account.useProxy()) {
281     QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
282     sock->setProxy(proxy);
283   }
284 #endif
285
286   _socket = sock;
287   connect(sock, SIGNAL(readyRead()), SLOT(coreHasData()));
288   connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected()));
289   connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
290   connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError)));
291   connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
292
293   emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName()));
294   sock->connectToHost(_account.hostName(), _account.port());
295 }
296
297 void CoreConnection::coreSocketConnected() {
298   // Phase One: Send client info and wait for core info
299
300   emit connectionMsg(tr("Synchronizing to core..."));
301
302   QVariantMap clientInit;
303   clientInit["MsgType"] = "ClientInit";
304   clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
305   clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
306   clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
307   clientInit["UseSsl"] = _account.useSsl();
308 #ifndef QT_NO_COMPRESS
309   clientInit["UseCompression"] = true;
310 #else
311   clientInit["UseCompression"] = false;
312 #endif
313
314   SignalProxy::writeDataToDevice(_socket, clientInit);
315 }
316
317 void CoreConnection::clientInitAck(const QVariantMap &msg) {
318   // Core has accepted our version info and sent its own. Let's see if we accept it as well...
319   uint ver = msg["ProtocolVersion"].toUInt();
320   if(ver < Quassel::buildInfo().clientNeedsProtocol) {
321     emit connectionError(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
322         "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
323     disconnectFromCore();
324     return;
325   }
326
327 #ifndef QT_NO_COMPRESS
328   if(msg["SupportsCompression"].toBool()) {
329     _socket->setProperty("UseCompression", true);
330   }
331 #endif
332
333   _coreMsgBuffer = msg;
334 #ifdef HAVE_SSL
335   if(currentAccount().useSsl()) {
336     if(msg["SupportSsl"].toBool()) {
337       QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
338       Q_ASSERT(sslSocket);
339       connect(sslSocket, SIGNAL(encrypted()), this, SLOT(sslSocketEncrypted()));
340       connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
341
342       sslSocket->startClientEncryption();
343     } else {
344       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."));
345       disconnectFromCore();
346     }
347     return;
348   }
349 #endif
350   // if we use SSL we wait for the next step until every SSL warning has been cleared
351   connectionReady();
352
353 }
354
355 void CoreConnection::connectionReady() {
356   if(!_coreMsgBuffer["Configured"].toBool()) {
357     // start wizard
358     emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
359   } else if(_coreMsgBuffer["LoginEnabled"].toBool()) {
360     loginToCore();
361   }
362   _coreMsgBuffer.clear();
363   resetWarningsHandler();
364 }
365
366 void CoreConnection::loginToCore() {
367   emit connectionMsg(tr("Logging in..."));
368   if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
369     emit userAuthenticationRequired(&_account);  // *must* be a synchronous call
370     if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
371       disconnectFromCore();
372       return;
373     }
374   }
375
376   QVariantMap clientLogin;
377   clientLogin["MsgType"] = "ClientLogin";
378   clientLogin["User"] = currentAccount().user();
379   clientLogin["Password"] = currentAccount().password();
380   SignalProxy::writeDataToDevice(_socket, clientLogin);
381 }
382
383 void CoreConnection::loginFailed(const QString &error) {
384   emit userAuthenticationRequired(&_account, error);  // *must* be a synchronous call
385   if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
386     disconnectFromCore();
387     return;
388   }
389   loginToCore();
390 }
391
392 void CoreConnection::loginSuccess() {
393   updateProgress(0, 0);
394   setProgressText(tr("Receiving session state"));
395   setState(Synchronizing);
396   emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
397 }
398
399 void CoreConnection::sessionStateReceived(const QVariantMap &state) {
400   updateProgress(100, 100);
401
402   // rest of communication happens through SignalProxy...
403   disconnect(_socket, SIGNAL(readyRead()), this, 0);
404   disconnect(_socket, SIGNAL(connected()), this, 0);
405
406   //Client::instance()->setConnectedToCore(currentAccount().accountId(), _socket);
407   syncToCore(state);
408 }
409
410 void CoreConnection::syncToCore(const QVariantMap &sessionState) {
411   setProgressText(tr("Receiving network states"));
412   updateProgress(0, 100);
413
414   // create identities
415   foreach(QVariant vid, sessionState["Identities"].toList()) {
416     Client::instance()->coreIdentityCreated(vid.value<Identity>());
417   }
418
419   // create buffers
420   // FIXME: get rid of this crap -- why?
421   QVariantList bufferinfos = sessionState["BufferInfos"].toList();
422   NetworkModel *networkModel = Client::networkModel();
423   Q_ASSERT(networkModel);
424   foreach(QVariant vinfo, bufferinfos)
425     networkModel->bufferUpdated(vinfo.value<BufferInfo>());  // create BufferItems
426
427   QVariantList networkids = sessionState["NetworkIds"].toList();
428
429   // prepare sync progress thingys...
430   // FIXME: Care about removal of networks
431   _numNetsToSync = networkids.count();
432   updateProgress(0, _numNetsToSync);
433
434   // create network objects
435   foreach(QVariant networkid, networkids) {
436     NetworkId netid = networkid.value<NetworkId>();
437     if(Client::network(netid))
438       continue;
439     Network *net = new Network(netid, Client::instance());
440     _netsToSync.insert(net);
441     connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
442     connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
443     Client::addNetwork(net);
444   }
445   checkSyncState();
446 }
447
448 void CoreConnection::networkInitDone() {
449   Network *net = qobject_cast<Network *>(sender());
450   Q_ASSERT(net);
451   disconnect(net, 0, this, 0);
452   _netsToSync.remove(net);
453   updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
454   checkSyncState();
455 }
456
457 void CoreConnection::checkSyncState() {
458   if(_netsToSync.isEmpty()) {
459     setState(Synchronized);
460     setProgressText(QString());
461     setProgressMaximum(-1);
462     emit synchronized();
463   }
464 }