{
_autoReconnectTimer.setSingleShot(true);
- // TODO make configurable
- _whoTimer.setInterval(60 * 1000);
- _whoTimer.setSingleShot(false);
+ _previousConnectionAttemptFailed = false;
+ _lastUsedServerlistIndex = 0;
+
+ // TODO make autowho configurable (possibly per-network)
+ _autoWhoEnabled = true;
+ _autoWhoInterval = 90;
+ _autoWhoNickLimit = 0; // unlimited
+ _autoWhoDelay = 3;
+
+ _autoWhoTimer.setInterval(_autoWhoDelay * 1000);
+ _autoWhoTimer.setSingleShot(false);
+ _autoWhoCycleTimer.setInterval(_autoWhoInterval * 1000);
+ _autoWhoCycleTimer.setSingleShot(false);
+
+ // TokenBucket to avaid sending too much at once
+ _messagesPerSecond = 1;
+ _burstSize = 5;
+ _tokenBucket = 5; // init with a full bucket
+
+ _tokenBucketTimer.start(_messagesPerSecond * 1000);
+ _tokenBucketTimer.setSingleShot(false);
+
+ QHash<QString, QString> channels = coreSession()->persistentChannels(networkId());
+ foreach(QString chan, channels.keys()) {
+ _channelKeys[chan.toLower()] = channels[chan];
+ }
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()));
connect(network, SIGNAL(autoReconnectIntervalSet(quint32)), this, SLOT(autoReconnectSettingsChanged()));
connect(network, SIGNAL(autoReconnectRetriesSet(quint16)), this, SLOT(autoReconnectSettingsChanged()));
+#ifndef QT_NO_OPENSSL
+ connect(&socket, SIGNAL(encrypted()), this, SLOT(socketEncrypted()));
+ connect(&socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
+#endif
connect(&socket, SIGNAL(connected()), this, SLOT(socketConnected()));
+
connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
connect(_ircServerHandler, SIGNAL(nickChanged(const QString &, const QString &)),
this, SLOT(nickChanged(const QString &, const QString &)));
+
+ network->proxy()->attachSignal(this, SIGNAL(sslErrors(const QVariant &)));
}
NetworkConnection::~NetworkConnection() {
if(connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting)
- disconnectFromIrc();
+ disconnectFromIrc(false); // clean up, but this does not count as requested disconnect!
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);
}
qWarning() << "Invalid identity configures, ignoring connect request!";
return;
}
- // TODO implement cycling / random servers
- QString host = serverList[0].toMap()["Host"].toString();
- quint16 port = serverList[0].toMap()["Port"].toUInt();
+ // use a random server?
+ if(network()->useRandomServer()) {
+ _lastUsedServerlistIndex = qrand() % serverList.size();
+ } else if(_previousConnectionAttemptFailed) {
+ // cycle to next server if previous connection attempt failed
+ displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connection failed. Cycling to next Server"));
+ if(++_lastUsedServerlistIndex == serverList.size()) {
+ _lastUsedServerlistIndex = 0;
+ }
+ }
+ _previousConnectionAttemptFailed = false;
+
+ QString host = serverList[_lastUsedServerlistIndex].toMap()["Host"].toString();
+ quint16 port = serverList[_lastUsedServerlistIndex].toMap()["Port"].toUInt();
displayStatusMsg(tr("Connecting to %1:%2...").arg(host).arg(port));
displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(host).arg(port));
socket.connectToHost(host, port);
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() {
// rejoin channels we've been in
QStringList channels, keys;
- foreach(QString chan, network()->persistentChannels().keys()) {
- QString key = network()->persistentChannels()[chan];
+ foreach(QString chan, persistentChannels()) {
+ QString key = channelKey(chan);
if(!key.isEmpty()) {
channels.prepend(chan); keys.prepend(key);
} else {
channels.append(chan);
}
}
- userInputHandler()->handleJoin(statusBuf, QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")));
+ QString joinString = QString("%1 %2").arg(channels.join(",")).arg(keys.join(",")).trimmed();
+ if(!joinString.isEmpty()) userInputHandler()->handleJoin(statusBuf, joinString);
}
-void NetworkConnection::disconnectFromIrc() {
+void NetworkConnection::disconnectFromIrc(bool requested) {
_autoReconnectTimer.stop();
_autoReconnectCount = 0;
displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting."));
setConnectionState(Network::Disconnected);
socketDisconnected();
} else socket.disconnectFromHost();
+
+ if(requested) {
+ emit quitRequested(networkId());
+ }
}
void NetworkConnection::socketHasData() {
}
void NetworkConnection::socketError(QAbstractSocket::SocketError) {
+ _previousConnectionAttemptFailed = true;
qDebug() << qPrintable(tr("Could not connect to %1 (%2)").arg(network()->networkName(), socket.errorString()));
emit connectionError(socket.errorString());
emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
setConnectionState(Network::Disconnected);
socketDisconnected();
}
+ // mark last connection attempt as failed
+
+ //qDebug() << "exiting...";
+ //exit(1);
}
+#ifndef QT_NO_OPENSSL
+
+void NetworkConnection::sslErrors(const QList<QSslError> &sslErrors) {
+ socket.ignoreSslErrors();
+ /* TODO errorhandling
+ QVariantMap errmsg;
+ QVariantList errnums;
+ foreach(QSslError err, errors) errnums << err.error();
+ errmsg["SslErrors"] = errnums;
+ errmsg["SslCert"] = socket.peerCertificate().toPem();
+ errmsg["PeerAddress"] = socket.peerAddress().toString();
+ errmsg["PeerPort"] = socket.peerPort();
+ errmsg["PeerName"] = socket.peerName();
+ emit sslErrors(errmsg);
+ disconnectFromIrc();
+ */
+}
+
+void NetworkConnection::socketEncrypted() {
+ //qDebug() << "encrypted!";
+ socketInitialized();
+}
+
+#endif // QT_NO_OPENSSL
+
void NetworkConnection::socketConnected() {
+#ifdef QT_NO_OPENSSL
+ socketInitialized();
+ return;
+#else
+ if(!network()->serverList()[_lastUsedServerlistIndex].toMap()["UseSSL"].toBool()) {
+ socketInitialized();
+ return;
+ }
+ //qDebug() << "starting handshake";
+ socket.startClientEncryption();
+#endif
+}
+
+void NetworkConnection::socketInitialized() {
//emit connected(networkId()); initialize first!
Identity *identity = coreSession()->identity(network()->identity());
if(!identity) {
disconnectFromIrc();
return;
}
+ QString passwd = network()->serverList()[_lastUsedServerlistIndex].toMap()["Password"].toString();
+ if(!passwd.isEmpty()) {
+ putRawLine(serverEncode(QString("PASS %1").arg(passwd)));
+ }
putRawLine(serverEncode(QString("NICK :%1").arg(identity->nicks()[0]))); // FIXME: try more nicks if error occurs
putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
}
}
void NetworkConnection::socketDisconnected() {
- _whoTimer.stop();
+ _autoWhoCycleTimer.stop();
+ _autoWhoTimer.stop();
+ _autoWhoQueue.clear();
+ _autoWhoInProgress.clear();
+
network()->setConnected(false);
emit disconnected(networkId());
- if(_autoReconnectCount == 0) emit quitRequested(networkId());
- else {
+ if(_autoReconnectCount != 0) {
setConnectionState(Network::Reconnecting);
if(_autoReconnectCount == network()->autoReconnectRetries()) doAutoReconnect(); // first try is immediate
else _autoReconnectTimer.start();
}
void NetworkConnection::putRawLine(QByteArray s) {
+ if(_tokenBucket > 0) {
+ writeToSocket(s);
+ } else {
+ _msgQueue.append(s);
+ }
+}
+
+void NetworkConnection::writeToSocket(QByteArray s) {
s += "\r\n";
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) {
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.insert(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) {
+ return _autoWhoInProgress.remove(channel);
+}
+
+void NetworkConnection::setChannelJoined(const QString &channel) {
+ emit channelJoined(networkId(), channel, _channelKeys[channel.toLower()]);
+ _autoWhoQueue.prepend(channel); // prepend so this new chan is the first to be checked
+}
+
+void NetworkConnection::setChannelParted(const QString &channel) {
+ removeChannelKey(channel);
+ _autoWhoQueue.removeAll(channel);
+ _autoWhoInProgress.remove(channel);
+ emit channelParted(networkId(), channel);
}
void NetworkConnection::addChannelKey(const QString &channel, const QString &key) {
- if(key.isEmpty()) removeChannelKey(channel);
- else _channelKeys[channel] = key;
+ if(key.isEmpty()) {
+ removeChannelKey(channel);
+ } else {
+ _channelKeys[channel.toLower()] = key;
+ }
}
void NetworkConnection::removeChannelKey(const QString &channel) {
- _channelKeys.remove(channel);
+ _channelKeys.remove(channel.toLower());
}
void NetworkConnection::nickChanged(const QString &newNick, const QString &oldNick) {
- emit nickChanged(_network->networkId(), newNick, oldNick);
+ emit nickChanged(networkId(), newNick, oldNick);
}
/* Exception classes for message handling */