#include "userinputhandler.h"
#include "ctcphandler.h"
-NetworkConnection::NetworkConnection(Network *network, CoreSession *session) : QObject(network),
+NetworkConnection::NetworkConnection(Network *network, CoreSession *session)
+ : QObject(network),
_connectionState(Network::Disconnected),
_network(network),
_coreSession(session),
_ircServerHandler(new IrcServerHandler(this)),
_userInputHandler(new UserInputHandler(this)),
_ctcpHandler(new CtcpHandler(this)),
- _autoReconnectCount(0)
+ _autoReconnectCount(0),
+ _quitRequested(false),
+
+ _previousConnectionAttemptFailed(false),
+ _lastUsedServerlistIndex(0),
+
+ // TODO make autowho configurable (possibly per-network)
+ _autoWhoEnabled(true),
+ _autoWhoInterval(90),
+ _autoWhoNickLimit(0), // unlimited
+ _autoWhoDelay(3),
+
+ // TokenBucket to avaid sending too much at once
+ _messagesPerSecond(1),
+ _burstSize(5),
+ _tokenBucket(5), // init with a full bucket
+
+ // TODO:
+ // should be 510 (2 bytes are added when writing to the socket)
+ // maxMsgSize is 510 minus the hostmask which will be added by the server
+ _maxMsgSize(450)
{
_autoReconnectTimer.setSingleShot(true);
- _previousConnectionAttemptFailed = false;
- _lastUsedServerlistIndex = 0;
- // TODO make configurable
- _whoTimer.setInterval(60 * 1000);
- _whoTimer.setSingleShot(false);
+ _socketCloseTimer.setSingleShot(true);
+ connect(&_socketCloseTimer, SIGNAL(timeout()), this, SLOT(socketCloseTimeout()));
+
+ _autoWhoTimer.setInterval(_autoWhoDelay * 1000);
+ _autoWhoCycleTimer.setInterval(_autoWhoInterval * 1000);
+
+ _tokenBucketTimer.start(_messagesPerSecond * 1000);
QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
foreach(QString chan, channels.keys()) {
}
connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
- connect(&_whoTimer, SIGNAL(timeout()), this, SLOT(sendWho()));
+ connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho()));
+ connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle()));
+ connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue()));
connect(network, SIGNAL(currentServerSet(const QString &)), this, SLOT(networkInitialized(const QString &)));
connect(network, SIGNAL(useAutoReconnectSet(bool)), this, SLOT(autoReconnectSettingsChanged()));
NetworkConnection::~NetworkConnection() {
if(connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting)
disconnectFromIrc(false); // clean up, but this does not count as requested disconnect!
+ disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
delete _ircServerHandler;
delete _userInputHandler;
delete _ctcpHandler;
}
-bool NetworkConnection::isConnected() const {
- // return socket.state() == QAbstractSocket::ConnectedState;
- return connectionState() == Network::Initialized;
-}
-
-Network::ConnectionState NetworkConnection::connectionState() const {
- return _connectionState;
-}
-
void NetworkConnection::setConnectionState(Network::ConnectionState state) {
_connectionState = state;
network()->setConnectionState(state);
emit connectionStateChanged(state);
}
-NetworkId NetworkConnection::networkId() const {
- return network()->networkId();
-}
-
-QString NetworkConnection::networkName() const {
- return network()->networkName();
-}
-
-Identity *NetworkConnection::identity() const {
- return coreSession()->identity(network()->identity());
-}
-
-Network *NetworkConnection::network() const {
- return _network;
-}
-
-CoreSession *NetworkConnection::coreSession() const {
- return _coreSession;
-}
-
-IrcServerHandler *NetworkConnection::ircServerHandler() const {
- return _ircServerHandler;
-}
-
-UserInputHandler *NetworkConnection::userInputHandler() const {
- return _userInputHandler;
-}
-
-CtcpHandler *NetworkConnection::ctcpHandler() const {
- return _ctcpHandler;
-}
-
QString NetworkConnection::serverDecode(const QByteArray &string) const {
return network()->decodeServerString(string);
}
setConnectionState(Network::Initialized);
network()->setConnected(true);
emit connected(networkId());
- sendWho();
- _whoTimer.start();
+
+ if(_autoWhoEnabled) {
+ _autoWhoCycleTimer.start();
+ _autoWhoTimer.start();
+ startAutoWhoCycle(); // FIXME wait for autojoin to be completed
+ }
}
void NetworkConnection::sendPerform() {
if(socket.state() < QAbstractSocket::ConnectedState) {
setConnectionState(Network::Disconnected);
socketDisconnected();
- } else socket.disconnectFromHost();
-
- if(requested) {
- emit quitRequested(networkId());
+ } else {
+ _socketCloseTimer.start(10000); // the irc server has 10 seconds to close the socket
}
+
+ // this flag triggers quitRequested() once the socket is closed
+ // it is needed to determine whether or not the connection needs to be
+ // in the automatic session restore.
+ _quitRequested = requested;
}
void NetworkConnection::socketHasData() {
#ifndef QT_NO_OPENSSL
-void NetworkConnection::sslErrors(const QList<QSslError> &errors) {
+void NetworkConnection::sslErrors(const QList<QSslError> &sslErrors) {
+ Q_UNUSED(sslErrors)
socket.ignoreSslErrors();
/* TODO errorhandling
QVariantMap errmsg;
setConnectionState(state);
}
+void NetworkConnection::socketCloseTimeout() {
+ socket.disconnectFromHost();
+}
+
void NetworkConnection::socketDisconnected() {
- _whoTimer.stop();
+ _autoWhoCycleTimer.stop();
+ _autoWhoTimer.stop();
+ _autoWhoQueue.clear();
+ _autoWhoInProgress.clear();
+
+ _socketCloseTimer.stop();
+
network()->setConnected(false);
emit disconnected(networkId());
if(_autoReconnectCount != 0) {
setConnectionState(Network::Reconnecting);
if(_autoReconnectCount == network()->autoReconnectRetries()) doAutoReconnect(); // first try is immediate
else _autoReconnectTimer.start();
+ } else if(_quitRequested) {
+ emit quitRequested(networkId());
}
}
}
void NetworkConnection::putRawLine(QByteArray s) {
+ if(_tokenBucket > 0) {
+ // qDebug() << "putRawLine: " << s;
+ writeToSocket(s);
+ } else {
+ _msgQueue.append(s);
+ }
+}
+
+void NetworkConnection::writeToSocket(QByteArray s) {
s += "\r\n";
+ // qDebug() << "writeToSocket: " << s.size();
socket.write(s);
+ _tokenBucket--;
+}
+
+void NetworkConnection::fillBucketAndProcessQueue() {
+ if(_tokenBucket < _burstSize) {
+ _tokenBucket++;
+ }
+
+ while(_msgQueue.size() > 0 && _tokenBucket > 0) {
+ writeToSocket(_msgQueue.takeFirst());
+ }
}
void NetworkConnection::putCmd(const QString &cmd, const QVariantList ¶ms, const QByteArray &prefix) {
if(!params.isEmpty())
msg += " :" + params.last().toByteArray();
+ if(cmd == "PRIVMSG" && params.count() > 1) {
+ QByteArray msghead = "PRIVMSG " + params[0].toByteArray() + " :";
+
+ while (msg.size() > _maxMsgSize) {
+ QByteArray splitter(" .,-");
+ int splitPosition = 0;
+ for(int i = 0; i < splitter.size(); i++) {
+ splitPosition = qMax(splitPosition, msg.lastIndexOf(splitter[i], _maxMsgSize));
+ }
+ if(splitPosition < 300) {
+ splitPosition = _maxMsgSize;
+ }
+ putRawLine(msg.left(splitPosition));
+ msg = msghead + msg.mid(splitPosition);
+ }
+ }
+
putRawLine(msg);
}
-void NetworkConnection::sendWho() {
- foreach(QString chan, network()->channels()) {
+void NetworkConnection::sendAutoWho() {
+ while(!_autoWhoQueue.isEmpty()) {
+ QString chan = _autoWhoQueue.takeFirst();
+ IrcChannel *ircchan = network()->ircChannel(chan);
+ if(!ircchan) continue;
+ if(_autoWhoNickLimit > 0 && ircchan->ircUsers().count() > _autoWhoNickLimit) continue;
+ _autoWhoInProgress[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 NetworkConnection::startAutoWhoCycle() {
+ if(!_autoWhoQueue.isEmpty()) {
+ _autoWhoCycleTimer.stop();
+ return;
+ }
+ _autoWhoQueue = network()->channels();
+}
+
+bool NetworkConnection::setAutoWhoDone(const QString &channel) {
+ if(_autoWhoInProgress.value(channel.toLower(), 0) <= 0) return false;
+ _autoWhoInProgress[channel.toLower()]--;
+ return true;
+}
+
void NetworkConnection::setChannelJoined(const QString &channel) {
emit channelJoined(networkId(), channel, _channelKeys[channel.toLower()]);
+ _autoWhoQueue.prepend(channel.toLower()); // prepend so this new chan is the first to be checked
}
void NetworkConnection::setChannelParted(const QString &channel) {
removeChannelKey(channel);
+ _autoWhoQueue.removeAll(channel.toLower());
+ _autoWhoInProgress.remove(channel.toLower());
emit channelParted(networkId(), channel);
}