- _autoReconnectCount = autoReconnectRetries();
- }
-}
-
-void CoreNetwork::doAutoReconnect() {
- if(connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
- qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!";
- return;
- }
- if(_autoReconnectCount > 0)
- _autoReconnectCount--;
- connectToIrc(true);
-}
-
-void CoreNetwork::sendPing() {
- uint now = QDateTime::currentDateTime().toTime_t();
- if(_pingCount != 0) {
- qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
- << "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
- }
- if(_pingCount >= _maxPingCount && now - _lastPingTime <= (uint)(_pingTimer.interval() / 1000) + 1) {
- // the second check compares the actual elapsed time since the last ping and the pingTimer interval
- // if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
- // and unable to even handle a ping answer. So we ignore those misses.
- disconnectFromIrc(false, QString("No Ping reply in %1 seconds.").arg(_maxPingCount * _pingTimer.interval() / 1000), true /* withReconnect */);
- } else {
- _lastPingTime = now;
- _pingCount++;
- userInputHandler()->handlePing(BufferInfo(), QString());
- }
-}
-
-void CoreNetwork::enablePingTimeout() {
- resetPingTimeout();
- _pingTimer.start();
-}
-
-void CoreNetwork::disablePingTimeout() {
- _pingTimer.stop();
- resetPingTimeout();
-}
-
-void CoreNetwork::sendAutoWho() {
- // Don't send autowho if there are still some pending
- if(_autoWhoPending.count())
- return;
-
- while(!_autoWhoQueue.isEmpty()) {
- QString chan = _autoWhoQueue.takeFirst();
- IrcChannel *ircchan = ircChannel(chan);
- if(!ircchan) continue;
- if(_autoWhoNickLimit > 0 && ircchan->ircUsers().count() > _autoWhoNickLimit) continue;
- _autoWhoPending[chan]++;
- putRawLine("WHO " + serverEncode(chan));
- if(_autoWhoQueue.isEmpty() && _autoWhoEnabled && !_autoWhoCycleTimer.isActive()) {
- // Timer was stopped, means a new cycle is due immediately
- _autoWhoCycleTimer.start();
- startAutoWhoCycle();
- }
- break;
- }
-}
-
-void CoreNetwork::startAutoWhoCycle() {
- if(!_autoWhoQueue.isEmpty()) {
- _autoWhoCycleTimer.stop();
- return;
- }
- _autoWhoQueue = channels();
+ addModes.remove(QRegExp(QString("[^%1]").arg(requestedAdd)));
+
+ if (requestedRemove.isEmpty())
+ removeModes = QString();
+ else
+ removeModes.remove(QRegExp(QString("[^%1]").arg(requestedRemove)));
+
+ // deduplicate
+ persistentAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
+ persistentRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
+
+ // update
+ persistentAdd.remove(QRegExp(QString("[%1]").arg(removeModes)));
+ persistentRemove.remove(QRegExp(QString("[%1]").arg(addModes)));
+
+ // update issued mode list
+ requestedAdd.remove(QRegExp(QString("[%1]").arg(addModes)));
+ requestedRemove.remove(QRegExp(QString("[%1]").arg(removeModes)));
+ _requestedUserModes = QString("%1-%2").arg(requestedAdd).arg(requestedRemove);
+
+ persistentAdd += addModes;
+ persistentRemove += removeModes;
+ Core::setUserModes(userId(), networkId(), QString("%1-%2").arg(persistentAdd).arg(persistentRemove));
+}
+
+
+void CoreNetwork::resetPersistentModes()
+{
+ _requestedUserModes = QString('-');
+ Core::setUserModes(userId(), networkId(), QString());
+}
+
+
+void CoreNetwork::setUseAutoReconnect(bool use)
+{
+ Network::setUseAutoReconnect(use);
+ if (!use)
+ _autoReconnectTimer.stop();
+}
+
+
+void CoreNetwork::setAutoReconnectInterval(quint32 interval)
+{
+ Network::setAutoReconnectInterval(interval);
+ _autoReconnectTimer.setInterval(interval * 1000);
+}
+
+
+void CoreNetwork::setAutoReconnectRetries(quint16 retries)
+{
+ Network::setAutoReconnectRetries(retries);
+ if (_autoReconnectCount != 0) {
+ if (unlimitedReconnectRetries())
+ _autoReconnectCount = -1;
+ else
+ _autoReconnectCount = autoReconnectRetries();
+ }
+}
+
+
+void CoreNetwork::doAutoReconnect()
+{
+ if (connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
+ qWarning() << "CoreNetwork::doAutoReconnect(): Cannot reconnect while not being disconnected!";
+ return;
+ }
+ if (_autoReconnectCount > 0 || _autoReconnectCount == -1)
+ _autoReconnectCount--; // -2 means we delay the next reconnect
+ connectToIrc(true);
+}
+
+
+void CoreNetwork::sendPing()
+{
+ uint now = QDateTime::currentDateTime().toTime_t();
+ if (_pingCount != 0) {
+ qDebug() << "UserId:" << userId() << "Network:" << networkName() << "missed" << _pingCount << "pings."
+ << "BA:" << socket.bytesAvailable() << "BTW:" << socket.bytesToWrite();
+ }
+ if ((int)_pingCount >= networkConfig()->maxPingCount() && now - _lastPingTime <= (uint)(_pingTimer.interval() / 1000) + 1) {
+ // the second check compares the actual elapsed time since the last ping and the pingTimer interval
+ // if the interval is shorter then the actual elapsed time it means that this thread was somehow blocked
+ // and unable to even handle a ping answer. So we ignore those misses.
+ disconnectFromIrc(false, QString("No Ping reply in %1 seconds.").arg(_pingCount * _pingTimer.interval() / 1000), true /* withReconnect */);
+ }
+ else {
+ _lastPingTime = now;
+ _pingCount++;
+ // Don't send pings until the network is initialized
+ if(_sendPings)
+ userInputHandler()->handlePing(BufferInfo(), QString());
+ }
+}
+
+
+void CoreNetwork::enablePingTimeout(bool enable)
+{
+ if (!enable)
+ disablePingTimeout();
+ else {
+ resetPingTimeout();
+ if (networkConfig()->pingTimeoutEnabled())
+ _pingTimer.start();
+ }
+}
+
+
+void CoreNetwork::disablePingTimeout()
+{
+ _pingTimer.stop();
+ _sendPings = false;
+ resetPingTimeout();
+}
+
+
+void CoreNetwork::setPingInterval(int interval)
+{
+ _pingTimer.setInterval(interval * 1000);
+}
+
+/******** IRCv3 Capability Negotiation ********/
+
+void CoreNetwork::serverCapAdded(const QString &capability)
+{
+ // Check if it's a known capability; if so, add it to the list
+ // Handle special cases first
+ if (capability == IrcCap::SASL) {
+ // Only request SASL if it's enabled
+ if (networkInfo().useSasl)
+ queueCap(capability);
+ } else if (IrcCap::knownCaps.contains(capability)) {
+ // Handling for general known capabilities
+ queueCap(capability);
+ }
+}
+
+void CoreNetwork::serverCapAcknowledged(const QString &capability)
+{
+ // This may be called multiple times in certain situations.
+
+ // Handle core-side configuration
+ if (capability == IrcCap::AWAY_NOTIFY) {
+ // away-notify enabled, stop the autoWho timers, handle manually
+ setAutoWhoEnabled(false);
+ }
+
+ // Handle capabilities that require further messages sent to the IRC server
+ // If you change this list, ALSO change the list in CoreNetwork::capsRequiringServerMessages
+ if (capability == IrcCap::SASL) {
+ // If SASL mechanisms specified, limit to what's accepted for authentication
+ // if the current identity has a cert set, use SASL EXTERNAL
+ // FIXME use event
+#ifdef HAVE_SSL
+ if (!identityPtr()->sslCert().isNull()) {
+ if (IrcCap::SaslMech::maybeSupported(capValue(IrcCap::SASL), IrcCap::SaslMech::EXTERNAL)) {
+ // EXTERNAL authentication supported, send request
+ putRawLine(serverEncode("AUTHENTICATE EXTERNAL"));
+ } else {
+ displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
+ tr("SASL EXTERNAL authentication not supported"));
+ sendNextCap();
+ }
+ } else {
+#endif
+ if (IrcCap::SaslMech::maybeSupported(capValue(IrcCap::SASL), IrcCap::SaslMech::PLAIN)) {
+ // PLAIN authentication supported, send request
+ // Only working with PLAIN atm, blowfish later
+ putRawLine(serverEncode("AUTHENTICATE PLAIN"));
+ } else {
+ displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
+ tr("SASL PLAIN authentication not supported"));
+ sendNextCap();
+ }
+#ifdef HAVE_SSL
+ }
+#endif
+ }
+}
+
+void CoreNetwork::serverCapRemoved(const QString &capability)
+{
+ // This may be called multiple times in certain situations.
+
+ // Handle special cases here
+ if (capability == IrcCap::AWAY_NOTIFY) {
+ // away-notify disabled, enable autoWho according to configuration
+ setAutoWhoEnabled(networkConfig()->autoWhoEnabled());
+ }
+}
+
+void CoreNetwork::queueCap(const QString &capability)
+{
+ // IRCv3 specs all use lowercase capability names
+ QString _capLowercase = capability.toLower();
+ if (!_capsQueued.contains(_capLowercase)) {
+ _capsQueued.append(_capLowercase);
+ }
+}
+
+QString CoreNetwork::takeQueuedCap()
+{
+ if (!_capsQueued.empty()) {
+ return _capsQueued.takeFirst();
+ } else {
+ return QString();
+ }
+}
+
+void CoreNetwork::beginCapNegotiation()
+{
+ // Don't begin negotiation if no capabilities are queued to request
+ if (!capNegotiationInProgress())
+ return;
+
+ _capNegotiationActive = true;
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
+ tr("Ready to negotiate (found: %1)").arg(caps().join(", ")));
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
+ tr("Negotiating capabilities (requesting: %1)...").arg(_capsQueued.join(", ")));
+ sendNextCap();
+}
+
+void CoreNetwork::sendNextCap()
+{
+ if (capNegotiationInProgress()) {
+ // Request the next capability and remove it from the list
+ // Handle one at a time so one capability failing won't NAK all of 'em
+ putRawLine(serverEncode(QString("CAP REQ :%1").arg(takeQueuedCap())));
+ } else {
+ // No pending desired capabilities, capability negotiation finished
+ // If SASL requested but not available, print a warning
+ if (networkInfo().useSasl && !capEnabled(IrcCap::SASL))
+ displayMsg(Message::Error, BufferInfo::StatusBuffer, "",
+ tr("SASL authentication currently not supported by server"));
+
+ if (_capNegotiationActive) {
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "",
+ tr("Capability negotiation finished (enabled: %1)").arg(capsEnabled().join(", ")));
+ _capNegotiationActive = false;
+ }
+
+ // If nick registration is already complete, CAP END is not required
+ if (!_capInitialNegotiationEnded) {
+ putRawLine(serverEncode(QString("CAP END")));
+ _capInitialNegotiationEnded = true;
+ }
+ }
+}
+
+/******** AutoWHO ********/
+
+void CoreNetwork::startAutoWhoCycle()
+{
+ if (!_autoWhoQueue.isEmpty()) {
+ _autoWhoCycleTimer.stop();
+ return;
+ }
+ _autoWhoQueue = channels();
+}
+
+void CoreNetwork::queueAutoWhoOneshot(const QString &channelOrNick)
+{
+ // Prepend so these new channels/nicks are the first to be checked
+ // Don't allow duplicates
+ if (!_autoWhoQueue.contains(channelOrNick.toLower())) {
+ _autoWhoQueue.prepend(channelOrNick.toLower());
+ }
+ if (capEnabled(IrcCap::AWAY_NOTIFY)) {
+ // When away-notify is active, the timer's stopped. Start a new cycle to who this channel.
+ setAutoWhoEnabled(true);
+ }
+}
+
+
+void CoreNetwork::setAutoWhoDelay(int delay)
+{
+ _autoWhoTimer.setInterval(delay * 1000);