Enable the ping timeout as soon as the connection to IRC is open.
[quassel.git] / src / core / corenetwork.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2013 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  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "corenetwork.h"
22
23 #include "core.h"
24 #include "coreidentity.h"
25 #include "corenetworkconfig.h"
26 #include "coresession.h"
27 #include "coreuserinputhandler.h"
28 #include "networkevent.h"
29
30 INIT_SYNCABLE_OBJECT(CoreNetwork)
31 CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
32     : Network(networkid, session),
33     _coreSession(session),
34     _userInputHandler(new CoreUserInputHandler(this)),
35     _autoReconnectCount(0),
36     _quitRequested(false),
37
38     _previousConnectionAttemptFailed(false),
39     _lastUsedServerIndex(0),
40
41     _lastPingTime(0),
42     _pingCount(0),
43     _sendPings(false),
44     _requestedUserModes('-')
45 {
46     _autoReconnectTimer.setSingleShot(true);
47     _socketCloseTimer.setSingleShot(true);
48     connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
49
50     setPingInterval(networkConfig()->pingInterval());
51     connect(&_pingTimer, SIGNAL(timeout()), this, SLOT(sendPing()));
52
53     setAutoWhoDelay(networkConfig()->autoWhoDelay());
54     setAutoWhoInterval(networkConfig()->autoWhoInterval());
55
56     QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
57     foreach(QString chan, channels.keys()) {
58         _channelKeys[chan.toLower()] = channels[chan];
59     }
60
61     connect(networkConfig(), SIGNAL(pingTimeoutEnabledSet(bool)), SLOT(enablePingTimeout(bool)));
62     connect(networkConfig(), SIGNAL(pingIntervalSet(int)), SLOT(setPingInterval(int)));
63     connect(networkConfig(), SIGNAL(autoWhoEnabledSet(bool)), SLOT(setAutoWhoEnabled(bool)));
64     connect(networkConfig(), SIGNAL(autoWhoIntervalSet(int)), SLOT(setAutoWhoInterval(int)));
65     connect(networkConfig(), SIGNAL(autoWhoDelaySet(int)), SLOT(setAutoWhoDelay(int)));
66
67     connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
68     connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho()));
69     connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle()));
70     connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue()));
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     connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
82
83     if (Quassel::isOptionSet("oidentd")) {
84         connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
85         connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)));
86     }
87 }
88
89
90 CoreNetwork::~CoreNetwork()
91 {
92     if (connectionState() != Disconnected && connectionState() != Network::Reconnecting)
93         disconnectFromIrc(false);  // clean up, but this does not count as requested disconnect!
94     disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
95     delete _userInputHandler;
96 }
97
98
99 QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const
100 {
101     if (!bufferName.isEmpty()) {
102         IrcChannel *channel = ircChannel(bufferName);
103         if (channel)
104             return channel->decodeString(string);
105     }
106     return decodeString(string);
107 }
108
109
110 QString CoreNetwork::userDecode(const QString &userNick, const QByteArray &string) const
111 {
112     IrcUser *user = ircUser(userNick);
113     if (user)
114         return user->decodeString(string);
115     return decodeString(string);
116 }
117
118
119 QByteArray CoreNetwork::channelEncode(const QString &bufferName, const QString &string) const
120 {
121     if (!bufferName.isEmpty()) {
122         IrcChannel *channel = ircChannel(bufferName);
123         if (channel)
124             return channel->encodeString(string);
125     }
126     return encodeString(string);
127 }
128
129
130 QByteArray CoreNetwork::userEncode(const QString &userNick, const QString &string) const
131 {
132     IrcUser *user = ircUser(userNick);
133     if (user)
134         return user->encodeString(string);
135     return encodeString(string);
136 }
137
138
139 void CoreNetwork::connectToIrc(bool reconnecting)
140 {
141     if (!reconnecting && useAutoReconnect() && _autoReconnectCount == 0) {
142         _autoReconnectTimer.setInterval(autoReconnectInterval() * 1000);
143         if (unlimitedReconnectRetries())
144             _autoReconnectCount = -1;
145         else
146             _autoReconnectCount = autoReconnectRetries();
147     }
148     if (serverList().isEmpty()) {
149         qWarning() << "Server list empty, ignoring connect request!";
150         return;
151     }
152     CoreIdentity *identity = identityPtr();
153     if (!identity) {
154         qWarning() << "Invalid identity configures, ignoring connect request!";
155         return;
156     }
157
158     // cleaning up old quit reason
159     _quitReason.clear();
160
161     // use a random server?
162     if (useRandomServer()) {
163         _lastUsedServerIndex = qrand() % serverList().size();
164     }
165     else if (_previousConnectionAttemptFailed) {
166         // cycle to next server if previous connection attempt failed
167         displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
168         if (++_lastUsedServerIndex >= serverList().size()) {
169             _lastUsedServerIndex = 0;
170         }
171     }
172     _previousConnectionAttemptFailed = false;
173
174     Server server = usedServer();
175     displayStatusMsg(tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
176     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(server.host).arg(server.port));
177
178     if (server.useProxy) {
179         QNetworkProxy proxy((QNetworkProxy::ProxyType)server.proxyType, server.proxyHost, server.proxyPort, server.proxyUser, server.proxyPass);
180         socket.setProxy(proxy);
181     }
182     else {
183         socket.setProxy(QNetworkProxy::NoProxy);
184     }
185
186 #ifdef HAVE_SSL
187     socket.setProtocol((QSsl::SslProtocol)server.sslVersion);
188     if (server.useSsl) {
189         CoreIdentity *identity = identityPtr();
190         if (identity) {
191             socket.setLocalCertificate(identity->sslCert());
192             socket.setPrivateKey(identity->sslKey());
193         }
194         socket.connectToHostEncrypted(server.host, server.port);
195     }
196     else {
197         socket.connectToHost(server.host, server.port);
198     }
199 #else
200     socket.connectToHost(server.host, server.port);
201 #endif
202 }
203
204
205 void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect)
206 {
207     _quitRequested = requested; // see socketDisconnected();
208     if (!withReconnect) {
209         _autoReconnectTimer.stop();
210         _autoReconnectCount = 0; // prohibiting auto reconnect
211     }
212     disablePingTimeout();
213     _msgQueue.clear();
214
215     IrcUser *me_ = me();
216     if (me_) {
217         QString awayMsg;
218         if (me_->isAway())
219             awayMsg = me_->awayMessage();
220         Core::setAwayMessage(userId(), networkId(), awayMsg);
221     }
222
223     if (reason.isEmpty() && identityPtr())
224         _quitReason = identityPtr()->quitReason();
225     else
226         _quitReason = reason;
227
228     displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason));
229     switch (socket.state()) {
230     case QAbstractSocket::ConnectedState:
231         userInputHandler()->issueQuit(_quitReason);
232         if (requested || withReconnect) {
233             // the irc server has 10 seconds to close the socket
234             _socketCloseTimer.start(10000);
235             break;
236         }
237     default:
238         socket.close();
239         socketDisconnected();
240     }
241 }
242
243
244 void CoreNetwork::userInput(BufferInfo buf, QString msg)
245 {
246     userInputHandler()->handleUserInput(buf, msg);
247 }
248
249
250 void CoreNetwork::putRawLine(QByteArray s)
251 {
252     if (_tokenBucket > 0)
253         writeToSocket(s);
254     else
255         _msgQueue.append(s);
256 }
257
258
259 void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> &params, const QByteArray &prefix)
260 {
261     QByteArray msg;
262
263     if (!prefix.isEmpty())
264         msg += ":" + prefix + " ";
265     msg += cmd.toUpper().toAscii();
266
267     for (int i = 0; i < params.size() - 1; i++) {
268         msg += " " + params[i];
269     }
270     if (!params.isEmpty())
271         msg += " :" + params.last();
272
273     putRawLine(msg);
274 }
275
276
277 void CoreNetwork::setChannelJoined(const QString &channel)
278 {
279     _autoWhoQueue.prepend(channel.toLower()); // prepend so this new chan is the first to be checked
280
281     Core::setChannelPersistent(userId(), networkId(), channel, true);
282     Core::setPersistentChannelKey(userId(), networkId(), channel, _channelKeys[channel.toLower()]);
283 }
284
285
286 void CoreNetwork::setChannelParted(const QString &channel)
287 {
288     removeChannelKey(channel);
289     _autoWhoQueue.removeAll(channel.toLower());
290     _autoWhoPending.remove(channel.toLower());
291
292     Core::setChannelPersistent(userId(), networkId(), channel, false);
293 }
294
295
296 void CoreNetwork::addChannelKey(const QString &channel, const QString &key)
297 {
298     if (key.isEmpty()) {
299         removeChannelKey(channel);
300     }
301     else {
302         _channelKeys[channel.toLower()] = key;
303     }
304 }
305
306
307 void CoreNetwork::removeChannelKey(const QString &channel)
308 {
309     _channelKeys.remove(channel.toLower());
310 }
311
312
313 #ifdef HAVE_QCA2
314 Cipher *CoreNetwork::cipher(const QString &target)
315 {
316     if (target.isEmpty())
317         return 0;
318
319     if (!Cipher::neededFeaturesAvailable())
320         return 0;
321
322     CoreIrcChannel *channel = qobject_cast<CoreIrcChannel *>(ircChannel(target));
323     if (channel) {
324         return channel->cipher();
325     }
326     CoreIrcUser *user = qobject_cast<CoreIrcUser *>(ircUser(target));
327     if (user) {
328         return user->cipher();
329     } else if (!isChannelName(target)) {
330         return qobject_cast<CoreIrcUser*>(newIrcUser(target))->cipher();
331     }
332     return 0;
333 }
334
335
336 QByteArray CoreNetwork::cipherKey(const QString &target) const
337 {
338     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
339     if (c)
340         return c->cipher()->key();
341
342     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
343     if (u)
344         return u->cipher()->key();
345
346     return QByteArray();
347 }
348
349
350 void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
351 {
352     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
353     if (c) {
354         c->setEncrypted(c->cipher()->setKey(key));
355         return;
356     }
357
358     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
359     if (!u && !isChannelName(target))
360         u = qobject_cast<CoreIrcUser*>(newIrcUser(target));
361
362     if (u) {
363         u->setEncrypted(u->cipher()->setKey(key));
364         return;
365     }
366 }
367
368
369 bool CoreNetwork::cipherUsesCBC(const QString &target)
370 {
371     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
372     if (c)
373         return c->cipher()->usesCBC();
374     CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
375     if (u)
376         return u->cipher()->usesCBC();
377
378     return false;
379 }
380 #endif /* HAVE_QCA2 */
381
382 bool CoreNetwork::setAutoWhoDone(const QString &channel)
383 {
384     QString chan = channel.toLower();
385     if (_autoWhoPending.value(chan, 0) <= 0)
386         return false;
387     if (--_autoWhoPending[chan] <= 0)
388         _autoWhoPending.remove(chan);
389     return true;
390 }
391
392
393 void CoreNetwork::setMyNick(const QString &mynick)
394 {
395     Network::setMyNick(mynick);
396     if (connectionState() == Network::Initializing)
397         networkInitialized();
398 }
399
400
401 void CoreNetwork::socketHasData()
402 {
403     while (socket.canReadLine()) {
404         QByteArray s = socket.readLine();
405         s.chop(2);
406         NetworkDataEvent *event = new NetworkDataEvent(EventManager::NetworkIncoming, this, s);
407 #if QT_VERSION >= 0x040700
408         event->setTimestamp(QDateTime::currentDateTimeUtc());
409 #else
410         event->setTimestamp(QDateTime::currentDateTime().toUTC());
411 #endif
412         emit newEvent(event);
413     }
414 }
415
416
417 void CoreNetwork::socketError(QAbstractSocket::SocketError error)
418 {
419     if (_quitRequested && error == QAbstractSocket::RemoteHostClosedError)
420         return;
421
422     _previousConnectionAttemptFailed = true;
423     qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
424     emit connectionError(socket.errorString());
425     displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
426     emitConnectionError(socket.errorString());
427     if (socket.state() < QAbstractSocket::ConnectedState) {
428         socketDisconnected();
429     }
430 }
431
432
433 void CoreNetwork::socketInitialized()
434 {
435     Server server = usedServer();
436 #ifdef HAVE_SSL
437     if (server.useSsl && !socket.isEncrypted())
438         return;
439 #endif
440
441     CoreIdentity *identity = identityPtr();
442     if (!identity) {
443         qCritical() << "Identity invalid!";
444         disconnectFromIrc();
445         return;
446     }
447
448     emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
449
450     enablePingTimeout();
451
452     // TokenBucket to avoid sending too much at once
453     _messageDelay = 2200;  // this seems to be a safe value (2.2 seconds delay)
454     _burstSize = 5;
455     _tokenBucket = _burstSize; // init with a full bucket
456     _tokenBucketTimer.start(_messageDelay);
457
458     if (networkInfo().useSasl) {
459         putRawLine(serverEncode(QString("CAP REQ :sasl")));
460     }
461     if (!server.password.isEmpty()) {
462         putRawLine(serverEncode(QString("PASS %1").arg(server.password)));
463     }
464     QString nick;
465     if (identity->nicks().isEmpty()) {
466         nick = "quassel";
467         qWarning() << "CoreNetwork::socketInitialized(): no nicks supplied for identity Id" << identity->id();
468     }
469     else {
470         nick = identity->nicks()[0];
471     }
472     putRawLine(serverEncode(QString("NICK :%1").arg(nick)));
473     putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
474 }
475
476
477 void CoreNetwork::socketDisconnected()
478 {
479     disablePingTimeout();
480     _msgQueue.clear();
481
482     _autoWhoCycleTimer.stop();
483     _autoWhoTimer.stop();
484     _autoWhoQueue.clear();
485     _autoWhoPending.clear();
486
487     _socketCloseTimer.stop();
488
489     _tokenBucketTimer.stop();
490
491     IrcUser *me_ = me();
492     if (me_) {
493         foreach(QString channel, me_->channels())
494         displayMsg(Message::Quit, BufferInfo::ChannelBuffer, channel, _quitReason, me_->hostmask());
495     }
496
497     setConnected(false);
498     emit disconnected(networkId());
499     emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort());
500     if (_quitRequested) {
501         _quitRequested = false;
502         setConnectionState(Network::Disconnected);
503         Core::setNetworkConnected(userId(), networkId(), false);
504     }
505     else if (_autoReconnectCount != 0) {
506         setConnectionState(Network::Reconnecting);
507         if (_autoReconnectCount == -1 || _autoReconnectCount == autoReconnectRetries())
508             doAutoReconnect();  // first try is immediate
509         else
510             _autoReconnectTimer.start();
511     }
512 }
513
514
515 void CoreNetwork::socketStateChanged(QAbstractSocket::SocketState socketState)
516 {
517     Network::ConnectionState state;
518     switch (socketState) {
519     case QAbstractSocket::UnconnectedState:
520         state = Network::Disconnected;
521         break;
522     case QAbstractSocket::HostLookupState:
523     case QAbstractSocket::ConnectingState:
524         state = Network::Connecting;
525         break;
526     case QAbstractSocket::ConnectedState:
527         state = Network::Initializing;
528         break;
529     case QAbstractSocket::ClosingState:
530         state = Network::Disconnecting;
531         break;
532     default:
533         state = Network::Disconnected;
534     }
535     setConnectionState(state);
536 }
537
538
539 void CoreNetwork::networkInitialized()
540 {
541     setConnectionState(Network::Initialized);
542     setConnected(true);
543     _quitRequested = false;
544
545     if (useAutoReconnect()) {
546         // reset counter
547         _autoReconnectCount = unlimitedReconnectRetries() ? -1 : autoReconnectRetries();
548     }
549
550     // restore away state
551     QString awayMsg = Core::awayMessage(userId(), networkId());
552     if (!awayMsg.isEmpty())
553         userInputHandler()->handleAway(BufferInfo(), Core::awayMessage(userId(), networkId()));
554
555     sendPerform();
556
557     _sendPings = true;
558
559     if (networkConfig()->autoWhoEnabled()) {
560         _autoWhoCycleTimer.start();
561         _autoWhoTimer.start();
562         startAutoWhoCycle(); // FIXME wait for autojoin to be completed
563     }
564
565     Core::bufferInfo(userId(), networkId(), BufferInfo::StatusBuffer); // create status buffer
566     Core::setNetworkConnected(userId(), networkId(), true);
567 }
568
569
570 void CoreNetwork::sendPerform()
571 {
572     BufferInfo statusBuf = BufferInfo::fakeStatusBuffer(networkId());
573
574     // do auto identify
575     if (useAutoIdentify() && !autoIdentifyService().isEmpty() && !autoIdentifyPassword().isEmpty()) {
576         userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(autoIdentifyService(), autoIdentifyPassword()));
577     }
578
579     // restore old user modes if server default mode is set.
580     IrcUser *me_ = me();
581     if (me_) {
582         if (!me_->userModes().isEmpty()) {
583             restoreUserModes();
584         }
585         else {
586             connect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
587             connect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
588         }
589     }
590
591     // send perform list
592     foreach(QString line, perform()) {
593         if (!line.isEmpty()) userInput(statusBuf, line);
594     }
595
596     // rejoin channels we've been in
597     if (rejoinChannels()) {
598         QStringList channels, keys;
599         foreach(QString chan, coreSession()->persistentChannels(networkId()).keys()) {
600             QString key = channelKey(chan);
601             if (!key.isEmpty()) {
602                 channels.prepend(chan);
603                 keys.prepend(key);
604             }
605             else {
606                 channels.append(chan);
607             }
608         }
609         QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
610         if (!joinString.isEmpty())
611             userInputHandler()->handleJoin(statusBuf, joinString);
612     }
613 }
614
615
616 void CoreNetwork::restoreUserModes()
617 {
618     IrcUser *me_ = me();
619     Q_ASSERT(me_);
620
621     disconnect(me_, SIGNAL(userModesSet(QString)), this, SLOT(restoreUserModes()));
622     disconnect(me_, SIGNAL(userModesAdded(QString)), this, SLOT(restoreUserModes()));
623
624     QString modesDelta = Core::userModes(userId(), networkId());
625     QString currentModes = me_->userModes();
626
627     QString addModes, removeModes;
628     if (modesDelta.contains('-')) {
629         addModes = modesDelta.section('-', 0, 0);
630         removeModes = modesDelta.section('-', 1);
631     }
632     else {
633         addModes = modesDelta;
634     }
635
636     addModes.remove(QRegExp(QString("[%1]").arg(currentModes)));
637     if (currentModes.isEmpty())
638         removeModes = QString();
639     else
640         removeModes.remove(QRegExp(QString("[^%1]").arg(currentModes)));
641
642     if (addModes.isEmpty() && removeModes.isEmpty())
643         return;
644
645     if (!addModes.isEmpty())
646         addModes = '+' + addModes;
647     if (!removeModes.isEmpty())
648         removeModes = '-' + removeModes;
649
650     // don't use InputHandler::handleMode() as it keeps track of our persistent mode changes
651     putRawLine(serverEncode(QString("MODE %1 %2%3").arg(me_->nick()).arg(addModes).arg(removeModes)));
652 }
653
654
655 void CoreNetwork::updateIssuedModes(const QString &requestedModes)
656 {
657     QString addModes;
658     QString removeModes;
659     bool addMode = true;
660
661     for (int i = 0; i < requestedModes.length(); i++) {
662         if (requestedModes[i] == '+') {
663             addMode = true;
664             continue;
665         }
666         if (requestedModes[i] == '-') {
667             addMode = false;
668             continue;
669         }
670         if (addMode) {
671             addModes += requestedModes[i];
672         }
673         else {
674             removeModes += requestedModes[i];
675         }
676     }
677
678     QString addModesOld = _requestedUserModes.section('-', 0, 0);
679     QString removeModesOld = _requestedUserModes.section('-', 1);
680
681     addModes.remove(QRegExp(QString("[%1]").arg(addModesOld))); // deduplicate
682     addModesOld.remove(QRegExp(QString("[%1]").arg(removeModes))); // update
683     addModes += addModesOld;
684
685     removeModes.remove(QRegExp(QString("[%1]").arg(removeModesOld))); // deduplicate
686     removeModesOld.remove(QRegExp(QString("[%1]").arg(addModes))); // update
687     removeModes += removeModesOld;
688
689     _requestedUserModes = QString("%1-%2").arg(addModes).arg(removeModes);
690 }
691
692
693 void CoreNetwork::updatePersistentModes(QString addModes, QString removeModes)
694 {
695     QString persistentUserModes = Core::userModes(userId(), networkId());
696
697     QString requestedAdd = _requestedUserModes.section('-', 0, 0);
698     QString requestedRemove = _requestedUserModes.section('-', 1);
699
700     QString persistentAdd, persistentRemove;
701     if (persistentUserModes.contains('-')) {
702         persistentAdd = persistentUserModes.section('-', 0, 0);
703         persistentRemove = persistentUserModes.section('-', 1);
704     }
705     else {
706         persistentAdd = persistentUserModes;
707     }
708
709     // remove modes we didn't issue
710     if (requestedAdd.isEmpty())
711         addModes = QString();
712     else
713         addModes.remove(QRegExp(QString("[^%1]").arg(requestedAdd)));
714
715     if (requestedRemove.isEmpty())
716         removeModes = QString();
717     else
718         removeModes.remove(QRegExp(QString("[^%1]").arg(requestedRemove)));
719
720     // deduplicate
721     persistentAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
722     persistentRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
723
724     // update
725     persistentAdd.remove(QRegExp(QString("[%1]").arg(removeModes)));
726     persistentRemove.remove(QRegExp(QString("[%1]").arg(addModes)));
727
728     // update issued mode list
729     requestedAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
730     requestedRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
731     _requestedUserModes = QString("%1-%2").arg(requestedAdd).arg(requestedRemove);
732
733     persistentAdd += addModes;
734     persistentRemove += removeModes;
735     Core::setUserModes(userId(), networkId(), QString("%1-%2").arg(persistentAdd).arg(persistentRemove));
736 }
737
738
739 void CoreNetwork::resetPersistentModes()
740 {
741     _requestedUserModes = QString('-');
742     Core::setUserModes(userId(), networkId(), QString());
743 }
744
745
746 void CoreNetwork::setUseAutoReconnect(bool use)
747 {
748     Network::setUseAutoReconnect(use);
749     if (!use)
750         _autoReconnectTimer.stop();
751 }
752
753
754 void CoreNetwork::setAutoReconnectInterval(quint32 interval)
755 {
756     Network::setAutoReconnectInterval(interval);
757     _autoReconnectTimer.setInterval(interval * 1000);
758 }
759
760
761 void CoreNetwork::setAutoReconnectRetries(quint16 retries)
762 {
763     Network::setAutoReconnectRetries(retries);
764     if (_autoReconnectCount != 0) {
765         if (unlimitedReconnectRetries())
766             _autoReconnectCount = -1;
767         else
768             _autoReconnectCount = autoReconnectRetries();
769     }
770 }
771
772
773 void CoreNetwork::doAutoReconnect()
774 {
775     if (connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
776         qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!";
777         return;
778     }
779     if (_autoReconnectCount > 0 || _autoReconnectCount == -1)
780         _autoReconnectCount--;  // -2 means we delay the next reconnect
781     connectToIrc(true);
782 }
783
784
785 void CoreNetwork::sendPing()
786 {
787     uint now = QDateTime::currentDateTime().toTime_t();
788     if (_pingCount != 0) {
789         qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
790                  << "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
791     }
792     if ((int)_pingCount >= networkConfig()->maxPingCount() && now - _lastPingTime <= (uint)(_pingTimer.interval() / 1000) + 1) {
793         // the second check compares the actual elapsed time since the last ping and the pingTimer interval
794         // if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
795         // and unable to even handle a ping answer. So we ignore those misses.
796         disconnectFromIrc(false, QString("No Ping reply in %1 seconds.").arg(_pingCount * _pingTimer.interval() / 1000), true /* withReconnect */);
797     }
798     else {
799         _lastPingTime = now;
800         _pingCount++;
801         // Don't send pings until the network is initialized
802         if(_sendPings)
803             userInputHandler()->handlePing(BufferInfo(), QString());
804     }
805 }
806
807
808 void CoreNetwork::enablePingTimeout(bool enable)
809 {
810     if (!enable)
811         disablePingTimeout();
812     else {
813         resetPingTimeout();
814         if (networkConfig()->pingTimeoutEnabled())
815             _pingTimer.start();
816     }
817 }
818
819
820 void CoreNetwork::disablePingTimeout()
821 {
822     _pingTimer.stop();
823     _sendPings = false;
824     resetPingTimeout();
825 }
826
827
828 void CoreNetwork::setPingInterval(int interval)
829 {
830     _pingTimer.setInterval(interval * 1000);
831 }
832
833
834 /******** AutoWHO ********/
835
836 void CoreNetwork::startAutoWhoCycle()
837 {
838     if (!_autoWhoQueue.isEmpty()) {
839         _autoWhoCycleTimer.stop();
840         return;
841     }
842     _autoWhoQueue = channels();
843 }
844
845
846 void CoreNetwork::setAutoWhoDelay(int delay)
847 {
848     _autoWhoTimer.setInterval(delay * 1000);
849 }
850
851
852 void CoreNetwork::setAutoWhoInterval(int interval)
853 {
854     _autoWhoCycleTimer.setInterval(interval * 1000);
855 }
856
857
858 void CoreNetwork::setAutoWhoEnabled(bool enabled)
859 {
860     if (enabled && isConnected() && !_autoWhoTimer.isActive())
861         _autoWhoTimer.start();
862     else if (!enabled) {
863         _autoWhoTimer.stop();
864         _autoWhoCycleTimer.stop();
865     }
866 }
867
868
869 void CoreNetwork::sendAutoWho()
870 {
871     // Don't send autowho if there are still some pending
872     if (_autoWhoPending.count())
873         return;
874
875     while (!_autoWhoQueue.isEmpty()) {
876         QString chan = _autoWhoQueue.takeFirst();
877         IrcChannel *ircchan = ircChannel(chan);
878         if (!ircchan) continue;
879         if (networkConfig()->autoWhoNickLimit() > 0 && ircchan->ircUsers().count() >= networkConfig()->autoWhoNickLimit())
880             continue;
881         _autoWhoPending[chan]++;
882         putRawLine("WHO " + serverEncode(chan));
883         break;
884     }
885     if (_autoWhoQueue.isEmpty() && networkConfig()->autoWhoEnabled() && !_autoWhoCycleTimer.isActive()) {
886         // Timer was stopped, means a new cycle is due immediately
887         _autoWhoCycleTimer.start();
888         startAutoWhoCycle();
889     }
890 }
891
892
893 #ifdef HAVE_SSL
894 void CoreNetwork::sslErrors(const QList<QSslError> &sslErrors)
895 {
896     Q_UNUSED(sslErrors)
897     socket.ignoreSslErrors();
898     // TODO errorhandling
899 }
900
901
902 #endif  // HAVE_SSL
903
904 void CoreNetwork::fillBucketAndProcessQueue()
905 {
906     if (_tokenBucket < _burstSize) {
907         _tokenBucket++;
908     }
909
910     while (_msgQueue.size() > 0 && _tokenBucket > 0) {
911         writeToSocket(_msgQueue.takeFirst());
912     }
913 }
914
915
916 void CoreNetwork::writeToSocket(const QByteArray &data)
917 {
918     socket.write(data);
919     socket.write("\r\n");
920     _tokenBucket--;
921 }
922
923
924 Network::Server CoreNetwork::usedServer() const
925 {
926     if (_lastUsedServerIndex < serverList().count())
927         return serverList()[_lastUsedServerIndex];
928
929     if (!serverList().isEmpty())
930         return serverList()[0];
931
932     return Network::Server();
933 }
934
935
936 void CoreNetwork::requestConnect() const
937 {
938     if (connectionState() != Disconnected) {
939         qWarning() << "Requesting connect while already being connected!";
940         return;
941     }
942     QMetaObject::invokeMethod(const_cast<CoreNetwork *>(this), "connectToIrc", Qt::QueuedConnection);
943 }
944
945
946 void CoreNetwork::requestDisconnect() const
947 {
948     if (connectionState() == Disconnected) {
949         qWarning() << "Requesting disconnect while not being connected!";
950         return;
951     }
952     userInputHandler()->handleQuit(BufferInfo(), QString());
953 }
954
955
956 void CoreNetwork::requestSetNetworkInfo(const NetworkInfo &info)
957 {
958     Network::Server currentServer = usedServer();
959     setNetworkInfo(info);
960     Core::updateNetwork(coreSession()->user(), info);
961
962     // the order of the servers might have changed,
963     // so we try to find the previously used server
964     _lastUsedServerIndex = 0;
965     for (int i = 0; i < serverList().count(); i++) {
966         Network::Server server = serverList()[i];
967         if (server.host == currentServer.host && server.port == currentServer.port) {
968             _lastUsedServerIndex = i;
969             break;
970         }
971     }
972 }