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