Resurrect CoreConnectDlg
[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   if(isConnected())
243     return false;
244
245   CoreAccountSettings s;
246
247   if(!accId.isValid()) {
248     // check our settings and figure out what to do
249     if(!s.autoConnectOnStartup())
250       return false;
251     if(s.autoConnectToFixedAccount())
252       accId = s.autoConnectAccount();
253     else
254       accId = s.lastAccount();
255     if(!accId.isValid())
256       return false;
257   }
258   _account = accountModel()->account(accId);
259   if(!_account.accountId().isValid()) {
260     return false;
261   }
262
263   s.setLastAccount(accId);
264   connectToCurrentAccount();
265   return true;
266 }
267
268 void CoreConnection::connectToCurrentAccount() {
269   resetConnection();
270
271   Q_ASSERT(!_socket);
272 #ifdef HAVE_SSL
273   QSslSocket *sock = new QSslSocket(Client::instance());
274 #else
275   if(_account.useSsl()) {
276     emit connectionError(tr("<b>This client is built without SSL Support!</b><br />Disable the usage of SSL in the account settings."));
277     return;
278   }
279   QTcpSocket *sock = new QTcpSocket(Client::instance());
280 #endif
281
282 #ifndef QT_NO_NETWORKPROXY
283   if(_account.useProxy()) {
284     QNetworkProxy proxy(_account.proxyType(), _account.proxyHostName(), _account.proxyPort(), _account.proxyUser(), _account.proxyPassword());
285     sock->setProxy(proxy);
286   }
287 #endif
288
289   _socket = sock;
290   connect(sock, SIGNAL(readyRead()), SLOT(coreHasData()));
291   connect(sock, SIGNAL(connected()), SLOT(coreSocketConnected()));
292   connect(sock, SIGNAL(disconnected()), SLOT(coreSocketDisconnected()));
293   connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(coreSocketError(QAbstractSocket::SocketError)));
294   connect(sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
295
296   emit connectionMsg(tr("Connecting to %1...").arg(currentAccount().accountName()));
297   sock->connectToHost(_account.hostName(), _account.port());
298 }
299
300 void CoreConnection::coreSocketConnected() {
301   // Phase One: Send client info and wait for core info
302
303   emit connectionMsg(tr("Synchronizing to core..."));
304
305   QVariantMap clientInit;
306   clientInit["MsgType"] = "ClientInit";
307   clientInit["ClientVersion"] = Quassel::buildInfo().fancyVersionString;
308   clientInit["ClientDate"] = Quassel::buildInfo().buildDate;
309   clientInit["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
310   clientInit["UseSsl"] = _account.useSsl();
311 #ifndef QT_NO_COMPRESS
312   clientInit["UseCompression"] = true;
313 #else
314   clientInit["UseCompression"] = false;
315 #endif
316
317   SignalProxy::writeDataToDevice(_socket, clientInit);
318 }
319
320 void CoreConnection::clientInitAck(const QVariantMap &msg) {
321   // Core has accepted our version info and sent its own. Let's see if we accept it as well...
322   uint ver = msg["ProtocolVersion"].toUInt();
323   if(ver < Quassel::buildInfo().clientNeedsProtocol) {
324     emit connectionError(tr("<b>The Quassel Core you are trying to connect to is too old!</b><br>"
325         "Need at least core/client protocol v%1 to connect.").arg(Quassel::buildInfo().clientNeedsProtocol));
326     disconnectFromCore();
327     return;
328   }
329
330 #ifndef QT_NO_COMPRESS
331   if(msg["SupportsCompression"].toBool()) {
332     _socket->setProperty("UseCompression", true);
333   }
334 #endif
335
336   _coreMsgBuffer = msg;
337 #ifdef HAVE_SSL
338   if(currentAccount().useSsl()) {
339     if(msg["SupportSsl"].toBool()) {
340       QSslSocket *sslSocket = qobject_cast<QSslSocket *>(_socket);
341       Q_ASSERT(sslSocket);
342       connect(sslSocket, SIGNAL(encrypted()), this, SLOT(sslSocketEncrypted()));
343       connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
344
345       sslSocket->startClientEncryption();
346     } else {
347       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."));
348       disconnectFromCore();
349     }
350     return;
351   }
352 #endif
353   // if we use SSL we wait for the next step until every SSL warning has been cleared
354   connectionReady();
355
356 }
357
358 void CoreConnection::connectionReady() {
359   if(!_coreMsgBuffer["Configured"].toBool()) {
360     // start wizard
361     emit startCoreSetup(_coreMsgBuffer["StorageBackends"].toList());
362   } else if(_coreMsgBuffer["LoginEnabled"].toBool()) {
363     loginToCore();
364   }
365   _coreMsgBuffer.clear();
366   resetWarningsHandler();
367 }
368
369 void CoreConnection::loginToCore() {
370   emit connectionMsg(tr("Logging in..."));
371   if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
372     emit userAuthenticationRequired(&_account);  // *must* be a synchronous call
373     if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
374       disconnectFromCore();
375       return;
376     }
377   }
378
379   QVariantMap clientLogin;
380   clientLogin["MsgType"] = "ClientLogin";
381   clientLogin["User"] = currentAccount().user();
382   clientLogin["Password"] = currentAccount().password();
383   SignalProxy::writeDataToDevice(_socket, clientLogin);
384 }
385
386 void CoreConnection::loginFailed(const QString &error) {
387   emit userAuthenticationRequired(&_account, error);  // *must* be a synchronous call
388   if(currentAccount().user().isEmpty() || currentAccount().password().isEmpty()) {
389     disconnectFromCore();
390     return;
391   }
392   loginToCore();
393 }
394
395 void CoreConnection::loginSuccess() {
396   updateProgress(0, 0);
397   setProgressText(tr("Receiving session state"));
398   setState(Synchronizing);
399   emit connectionMsg(tr("Synchronizing to %1...").arg(currentAccount().accountName()));
400 }
401
402 void CoreConnection::sessionStateReceived(const QVariantMap &state) {
403   updateProgress(100, 100);
404
405   // rest of communication happens through SignalProxy...
406   disconnect(_socket, SIGNAL(readyRead()), this, 0);
407   disconnect(_socket, SIGNAL(connected()), this, 0);
408
409   //Client::instance()->setConnectedToCore(currentAccount().accountId(), _socket);
410   syncToCore(state);
411 }
412
413 void CoreConnection::syncToCore(const QVariantMap &sessionState) {
414   setProgressText(tr("Receiving network states"));
415   updateProgress(0, 100);
416
417   // create identities
418   foreach(QVariant vid, sessionState["Identities"].toList()) {
419     Client::instance()->coreIdentityCreated(vid.value<Identity>());
420   }
421
422   // create buffers
423   // FIXME: get rid of this crap -- why?
424   QVariantList bufferinfos = sessionState["BufferInfos"].toList();
425   NetworkModel *networkModel = Client::networkModel();
426   Q_ASSERT(networkModel);
427   foreach(QVariant vinfo, bufferinfos)
428     networkModel->bufferUpdated(vinfo.value<BufferInfo>());  // create BufferItems
429
430   QVariantList networkids = sessionState["NetworkIds"].toList();
431
432   // prepare sync progress thingys...
433   // FIXME: Care about removal of networks
434   _numNetsToSync = networkids.count();
435   updateProgress(0, _numNetsToSync);
436
437   // create network objects
438   foreach(QVariant networkid, networkids) {
439     NetworkId netid = networkid.value<NetworkId>();
440     if(Client::network(netid))
441       continue;
442     Network *net = new Network(netid, Client::instance());
443     _netsToSync.insert(net);
444     connect(net, SIGNAL(initDone()), SLOT(networkInitDone()));
445     connect(net, SIGNAL(destroyed()), SLOT(networkInitDone()));
446     Client::addNetwork(net);
447   }
448   checkSyncState();
449 }
450
451 void CoreConnection::networkInitDone() {
452   Network *net = qobject_cast<Network *>(sender());
453   Q_ASSERT(net);
454   disconnect(net, 0, this, 0);
455   _netsToSync.remove(net);
456   updateProgress(_numNetsToSync - _netsToSync.count(), _numNetsToSync);
457   checkSyncState();
458 }
459
460 void CoreConnection::checkSyncState() {
461   if(_netsToSync.isEmpty()) {
462     setState(Synchronized);
463     setProgressText(QString());
464     setProgressMaximum(-1);
465     emit synchronized();
466   }
467 }