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