b3cbfc090ab858237d041bb9b3fa5f4aa2e3f444
[quassel.git] / src / core / corenetwork.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 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 "corenetwork.h"
22
23
24 #include "core.h"
25 #include "coresession.h"
26 #include "identity.h"
27
28 #include "ircserverhandler.h"
29 #include "userinputhandler.h"
30 #include "ctcphandler.h"
31
32 CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
33   : Network(networkid, session),
34     _coreSession(session),
35     _ircServerHandler(new IrcServerHandler(this)),
36     _userInputHandler(new UserInputHandler(this)),
37     _ctcpHandler(new CtcpHandler(this)),
38     _autoReconnectCount(0),
39     _quitRequested(false),
40
41     _previousConnectionAttemptFailed(false),
42     _lastUsedServerIndex(0),
43
44     // TODO make autowho configurable (possibly per-network)
45     _autoWhoEnabled(true),
46     _autoWhoInterval(90),
47     _autoWhoNickLimit(0), // unlimited
48     _autoWhoDelay(3)
49 {
50   _autoReconnectTimer.setSingleShot(true);
51   _socketCloseTimer.setSingleShot(true);
52   connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
53
54   _pingTimer.setInterval(60000);
55   connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
56
57   _autoWhoTimer.setInterval(_autoWhoDelay * 1000);
58   _autoWhoCycleTimer.setInterval(_autoWhoInterval * 1000);
59
60   QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
61   foreach(QString chan, channels.keys()) {
62     _channelKeys[chan.toLower()] = channels[chan];
63   }
64
65   connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
66   connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho()));
67   connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle()));
68   connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue()));
69   connect(this, SIGNAL(connectRequested()), this, SLOT(connectToIrc()));
70
71
72   connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized()));
73   connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
74   connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
75   connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
76   connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
77 #ifdef HAVE_SSL
78   connect(&socket, SIGNAL(encrypted()), this, SLOT(socketInitialized()));
79   connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
80 #endif
81 }
82
83 CoreNetwork::~CoreNetwork() {
84   if(connectionState() != Disconnected && connectionState() != Network::Reconnecting)
85     disconnectFromIrc(false);      // clean up, but this does not count as requested disconnect!
86   disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
87   delete _ircServerHandler;
88   delete _userInputHandler;
89   delete _ctcpHandler;
90 }
91
92 QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const {
93   if(!bufferName.isEmpty()) {
94     IrcChannel *channel = ircChannel(bufferName);
95     if(channel)
96       return channel->decodeString(string);
97   }
98   return decodeString(string);
99 }
100
101 QString CoreNetwork::userDecode(const QString &userNick, const QByteArray &string) const {
102   IrcUser *user = ircUser(userNick);
103   if(user)
104     return user->decodeString(string);
105   return decodeString(string);
106 }
107
108 QByteArray CoreNetwork::channelEncode(const QString &bufferName, const QString &string) const {
109   if(!bufferName.isEmpty()) {
110     IrcChannel *channel = ircChannel(bufferName);
111     if(channel)
112       return channel->encodeString(string);
113   }
114   return encodeString(string);
115 }
116
117 QByteArray CoreNetwork::userEncode(const QString &userNick, const QString &string) const {
118   IrcUser *user = ircUser(userNick);
119   if(user)
120     return user->encodeString(string);
121   return encodeString(string);
122 }
123
124 void CoreNetwork::connectToIrc(bool reconnecting) {
125   if(!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
126     _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
127     if(unlimitedReconnectRetries())
128       _autoReconnectCount = -1;
129     else
130       _autoReconnectCount = autoReconnectRetries();
131   }
132   if(serverList().isEmpty()) {
133     qWarning() << "Server list empty, ignoring connect request!";
134     return;
135   }
136   Identity *identity = identityPtr();
137   if(!identity) {
138     qWarning() << "Invalid identity configures, ignoring connect request!";
139     return;
140   }
141   // use a random server?
142   if(useRandomServer()) {
143     _lastUsedServerIndex = qrand() % serverList().size();
144   } else if(_previousConnectionAttemptFailed) {
145     // cycle to next server if previous connection attempt failed
146     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
147     if(++_lastUsedServerIndex == serverList().size()) {
148       _lastUsedServerIndex = 0;
149     }
150   }
151   _previousConnectionAttemptFailed = false;
152
153   Server server = usedServer();
154   displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
155   displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
156
157   if(server.useProxy) {
158     QNetworkProxy proxy((QNetworkProxy::ProxyType)server.proxyType, server.proxyHost, server.proxyPort, server.proxyUser, server.proxyPass);
159     socket.setProxy(proxy);
160   } else {
161     socket.setProxy(QNetworkProxy::NoProxy);
162   }
163
164 #ifdef HAVE_SSL
165   socket.setProtocol((QSsl::SslProtocol)server.sslVersion);
166   if(server.useSsl)
167     socket.connectToHostEncrypted(server.host, server.port);
168   else
169     socket.connectToHost(server.host, server.port);
170 #else
171   socket.connectToHost(server.host, server.port);
172 #endif
173 }
174
175 void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason) {
176   _quitRequested = requested; // see socketDisconnected();
177   _autoReconnectTimer.stop();
178   _autoReconnectCount = 0; // prohibiting auto reconnect
179   displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting."));
180   if(socket.state() == QAbstractSocket::UnconnectedState) {
181     socketDisconnected();
182   } else if(socket.state() < QAbstractSocket::ConnectedState || !requested) {
183     // we might be in a state waiting for a timeout...
184     // or (!requested) this is a core shutdown...
185     // in both cases we don't really care... set a disconnected state
186     socket.close();
187     socketDisconnected();
188   } else {
189     // quit gracefully if it's user requested quit
190     userInputHandler()->issueQuit(reason);
191     // the irc server has 10 seconds to close the socket
192     _socketCloseTimer.start(10000);
193   }
194 }
195
196 void CoreNetwork::userInput(BufferInfo buf, QString msg) {
197   userInputHandler()->handleUserInput(buf, msg);
198 }
199
200 void CoreNetwork::putRawLine(QByteArray s) {
201   if(_tokenBucket > 0)
202     writeToSocket(s);
203   else
204     _msgQueue.append(s);
205 }
206
207 void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> &params, const QByteArray &prefix) {
208   QByteArray msg;
209
210   if(!prefix.isEmpty())
211     msg += ":" + prefix + " ";
212   msg += cmd.toUpper().toAscii();
213
214   for(int i = 0; i < params.size() - 1; i++) {
215     msg += " " + params[i];
216   }
217   if(!params.isEmpty())
218     msg += " :" + params.last();
219
220   putRawLine(msg);
221 }
222
223 void CoreNetwork::setChannelJoined(const QString &channel) {
224   _autoWhoQueue.prepend(channel.toLower()); // prepend so this new chan is the first to be checked
225
226   Core::setChannelPersistent(userId(), networkId(), channel, true);
227   Core::setPersistentChannelKey(userId(), networkId(), channel, _channelKeys[channel.toLower()]);
228 }
229
230 void CoreNetwork::setChannelParted(const QString &channel) {
231   removeChannelKey(channel);
232   _autoWhoQueue.removeAll(channel.toLower());
233   _autoWhoInProgress.remove(channel.toLower());
234
235   Core::setChannelPersistent(userId(), networkId(), channel, false);
236 }
237
238 void CoreNetwork::addChannelKey(const QString &channel, const QString &key) {
239   if(key.isEmpty()) {
240     removeChannelKey(channel);
241   } else {
242     _channelKeys[channel.toLower()] = key;
243   }
244 }
245
246 void CoreNetwork::removeChannelKey(const QString &channel) {
247   _channelKeys.remove(channel.toLower());
248 }
249
250 bool CoreNetwork::setAutoWhoDone(const QString &channel) {
251   if(_autoWhoInProgress.value(channel.toLower(), 0) <= 0)
252     return false;
253   _autoWhoInProgress[channel.toLower()]--;
254   return true;
255 }
256
257 void CoreNetwork::setMyNick(const QString &mynick) {
258   Network::setMyNick(mynick);
259   if(connectionState() == Network::Initializing)
260     networkInitialized();
261 }
262
263 void CoreNetwork::socketHasData() {
264   while(socket.canReadLine()) {
265     QByteArray s = socket.readLine().trimmed();
266     ircServerHandler()->handleServerMsg(s);
267   }
268 }
269
270 void CoreNetwork::socketError(QAbstractSocket::SocketError) {
271   _previousConnectionAttemptFailed = true;
272   qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
273   emit connectionError(socket.errorString());
274   emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
275   emitConnectionError(socket.errorString());
276   if(socket.state() < QAbstractSocket::ConnectedState) {
277     socketDisconnected();
278   }
279 }
280
281 void CoreNetwork::socketInitialized() {
282   Server server = usedServer();
283 #ifdef HAVE_SSL
284   if(server.useSsl && !socket.isEncrypted())
285     return;
286 #endif
287
288   Identity *identity = identityPtr();
289   if(!identity) {
290     qCritical() << "Identity invalid!";
291     disconnectFromIrc();
292     return;
293   }
294
295   // TokenBucket to avoid sending too much at once
296   _messagesPerSecond = 1;
297   _burstSize = 5;
298   _tokenBucket = 5; // init with a full bucket
299   _tokenBucketTimer.start(_messagesPerSecond * 1000);
300
301   if(!server.password.isEmpty()) {
302     putRawLine(serverEncode(QString("PASS %1").arg(server.password)));
303   }
304   putRawLine(serverEncode(QString("NICK :%1").arg(identity->nicks()[0])));
305   putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
306 }
307
308 void CoreNetwork::socketDisconnected() {
309   _pingTimer.stop();
310   _autoWhoCycleTimer.stop();
311   _autoWhoTimer.stop();
312   _autoWhoQueue.clear();
313   _autoWhoInProgress.clear();
314
315   _socketCloseTimer.stop();
316
317   _tokenBucketTimer.stop();
318
319   IrcUser *me_ = me();
320   if(me_) {
321     foreach(QString channel, me_->channels())
322       emit displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, "", me_->hostmask());
323   }
324
325   setConnected(false);
326   emit disconnected(networkId());
327   if(_quitRequested) {
328     setConnectionState(Network::Disconnected);
329     Core::setNetworkConnected(userId(), networkId(), false);
330   } else if(_autoReconnectCount != 0) {
331     setConnectionState(Network::Reconnecting);
332     if(_autoReconnectCount == autoReconnectRetries())
333       doAutoReconnect(); // first try is immediate
334     else
335       _autoReconnectTimer.start();
336   }
337 }
338
339 void CoreNetwork::socketStateChanged(QAbstractSocket::SocketState socketState) {
340   Network::ConnectionState state;
341   switch(socketState) {
342     case QAbstractSocket::UnconnectedState:
343       state = Network::Disconnected;
344       break;
345     case QAbstractSocket::HostLookupState:
346     case QAbstractSocket::ConnectingState:
347       state = Network::Connecting;
348       break;
349     case QAbstractSocket::ConnectedState:
350       state = Network::Initializing;
351       break;
352     case QAbstractSocket::ClosingState:
353       state = Network::Disconnecting;
354       break;
355     default:
356       state = Network::Disconnected;
357   }
358   setConnectionState(state);
359 }
360
361 void CoreNetwork::networkInitialized() {
362   setConnectionState(Network::Initialized);
363   setConnected(true);
364
365   if(useAutoReconnect()) {
366     // reset counter
367     _autoReconnectCount = autoReconnectRetries();
368   }
369
370   sendPerform();
371
372   _pingTimer.start();
373
374   if(_autoWhoEnabled) {
375     _autoWhoCycleTimer.start();
376     _autoWhoTimer.start();
377     startAutoWhoCycle();  // FIXME wait for autojoin to be completed
378   }
379
380   Core::bufferInfo(userId(), networkId(), BufferInfo::StatusBuffer); // create status buffer
381   Core::setNetworkConnected(userId(), networkId(), true);
382 }
383
384 void CoreNetwork::sendPerform() {
385   BufferInfo statusBuf = BufferInfo::fakeStatusBuffer(networkId());
386
387   // do auto identify
388   if(useAutoIdentify() && !autoIdentifyService().isEmpty() && !autoIdentifyPassword().isEmpty()) {
389     userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(autoIdentifyService(), autoIdentifyPassword()));
390   }
391
392   // send perform list
393   foreach(QString line, perform()) {
394     if(!line.isEmpty()) userInput(statusBuf, line);
395   }
396
397   // rejoin channels we've been in
398   QStringList channels, keys;
399   foreach(QString chan, persistentChannels()) {
400     QString key = channelKey(chan);
401     if(!key.isEmpty()) {
402       channels.prepend(chan);
403       keys.prepend(key);
404     } else {
405       channels.append(chan);
406     }
407   }
408   QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
409   if(!joinString.isEmpty())
410     userInputHandler()->handleJoin(statusBuf, joinString);
411 }
412
413 void CoreNetwork::setUseAutoReconnect(bool use) {
414   Network::setUseAutoReconnect(use);
415   if(!use)
416     _autoReconnectTimer.stop();
417 }
418
419 void CoreNetwork::setAutoReconnectInterval(quint32 interval) {
420   Network::setAutoReconnectInterval(interval);
421   _autoReconnectTimer.setInterval(interval * 1000);
422 }
423
424 void CoreNetwork::setAutoReconnectRetries(quint16 retries) {
425   Network::setAutoReconnectRetries(retries);
426   if(_autoReconnectCount != 0) {
427     if(unlimitedReconnectRetries())
428       _autoReconnectCount = -1;
429     else
430       _autoReconnectCount = autoReconnectRetries();
431   }
432 }
433
434 void CoreNetwork::doAutoReconnect() {
435   if(connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
436     qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!";
437     return;
438   }
439   if(_autoReconnectCount > 0)
440     _autoReconnectCount--;
441   connectToIrc(true);
442 }
443
444 void CoreNetwork::sendPing() {
445   userInputHandler()->handlePing(BufferInfo(), QString());
446 }
447
448 void CoreNetwork::sendAutoWho() {
449   while(!_autoWhoQueue.isEmpty()) {
450     QString chan = _autoWhoQueue.takeFirst();
451     IrcChannel *ircchan = ircChannel(chan);
452     if(!ircchan) continue;
453     if(_autoWhoNickLimit > 0 && ircchan->ircUsers().count() > _autoWhoNickLimit) continue;
454     _autoWhoInProgress[chan]++;
455     putRawLine("WHO " + serverEncode(chan));
456     if(_autoWhoQueue.isEmpty() && _autoWhoEnabled && !_autoWhoCycleTimer.isActive()) {
457       // Timer was stopped, means a new cycle is due immediately
458       _autoWhoCycleTimer.start();
459       startAutoWhoCycle();
460     }
461     break;
462   }
463 }
464
465 void CoreNetwork::startAutoWhoCycle() {
466   if(!_autoWhoQueue.isEmpty()) {
467     _autoWhoCycleTimer.stop();
468     return;
469   }
470   _autoWhoQueue = channels();
471 }
472
473 #ifdef HAVE_SSL
474 void CoreNetwork::sslErrors(const QList<QSslError> &sslErrors) {
475   Q_UNUSED(sslErrors)
476   socket.ignoreSslErrors();
477   // TODO errorhandling
478 }
479 #endif  // HAVE_SSL
480
481 void CoreNetwork::fillBucketAndProcessQueue() {
482   if(_tokenBucket < _burstSize) {
483     _tokenBucket++;
484   }
485
486   while(_msgQueue.size() > 0 && _tokenBucket > 0) {
487     writeToSocket(_msgQueue.takeFirst());
488   }
489 }
490
491 void CoreNetwork::writeToSocket(const QByteArray &data) {
492   socket.write(data);
493   socket.write("\r\n");
494   _tokenBucket--;
495 }
496
497 Network::Server CoreNetwork::usedServer() const {
498   if(_lastUsedServerIndex < serverList().count())
499     return serverList()[_lastUsedServerIndex];
500   else
501     return Network::Server();
502 }
503
504 void CoreNetwork::requestConnect() const {
505   if(connectionState() != Disconnected) {
506     qWarning() << "Requesting connect while already being connected!";
507     return;
508   }
509   Network::requestConnect();
510 }
511
512 void CoreNetwork::requestDisconnect() const {
513   if(connectionState() == Disconnected) {
514     qWarning() << "Requesting disconnect while not being connected!";
515     return;
516   }
517   userInputHandler()->handleQuit(BufferInfo(), QString());
518 }
519
520 void CoreNetwork::requestSetNetworkInfo(const NetworkInfo &info) {
521   Network::Server currentServer = usedServer();
522   setNetworkInfo(info);
523   Core::updateNetwork(coreSession()->user(), info);
524
525   // update _lastUsedServerIndex;
526   for(int i = 0; i < serverList().count(); i++) {
527     Network::Server server = serverList()[i];
528     if(server.host == currentServer.host && server.port == currentServer.port) {
529       _lastUsedServerIndex = i;
530       break;
531     }
532   }
533 }