/***************************************************************************
- * Copyright (C) 2005-2014 by the Quassel Project *
+ * Copyright (C) 2005-2015 by the Quassel Project *
* devel@quassel-irc.org *
* *
* This program is free software; you can redistribute it and/or modify *
_userInputHandler(new CoreUserInputHandler(this)),
_autoReconnectCount(0),
_quitRequested(false),
+ _disconnectExpected(false),
_previousConnectionAttemptFailed(false),
_lastUsedServerIndex(0),
connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue()));
connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized()));
- 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(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *)));
if (Quassel::isOptionSet("oidentd")) {
- connect(this, SIGNAL(socketOpen(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
+ connect(this, SIGNAL(socketInitialized(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(addSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Qt::BlockingQueuedConnection);
connect(this, SIGNAL(socketDisconnected(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)), Core::instance()->oidentdConfigGenerator(), SLOT(removeSocket(const CoreIdentity*, QHostAddress, quint16, QHostAddress, quint16)));
}
}
CoreNetwork::~CoreNetwork()
{
- if (connectionState() != Disconnected && connectionState() != Network::Reconnecting)
- disconnectFromIrc(false); // clean up, but this does not count as requested disconnect!
+ // Request a proper disconnect, but don't count as user-requested disconnect
+ if (socketConnected()) {
+ // Only try if the socket's fully connected (not initializing or disconnecting).
+ // Force an immediate disconnect, jumping the command queue. Ensures the proper QUIT is
+ // shown even if other messages are queued.
+ disconnectFromIrc(false, QString(), false, true);
+ // Process the putCmd events that trigger the quit. Without this, shutting down the core
+ // results in abrubtly closing the socket rather than sending the QUIT as expected.
+ QCoreApplication::processEvents();
+ // Wait briefly for each network to disconnect. Sometimes it takes a little while to send.
+ if (!forceDisconnect()) {
+ qWarning() << "Timed out quitting network" << networkName() <<
+ "(user ID " << userId() << ")";
+ }
+ }
disconnect(&socket, 0, this, 0); // this keeps the socket from triggering events during clean up
delete _userInputHandler;
}
+bool CoreNetwork::forceDisconnect(int msecs)
+{
+ if (socket.state() == QAbstractSocket::UnconnectedState) {
+ // Socket already disconnected.
+ return true;
+ }
+ // Request a socket-level disconnect if not already happened
+ socket.disconnectFromHost();
+ // Return the result of waiting for disconnect; true if successful, otherwise false
+ return socket.waitForDisconnected(msecs);
+}
+
+
QString CoreNetwork::channelDecode(const QString &bufferName, const QByteArray &string) const
{
if (!bufferName.isEmpty()) {
}
-void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect)
+void CoreNetwork::disconnectFromIrc(bool requested, const QString &reason, bool withReconnect,
+ bool forceImmediate)
{
+ // Disconnecting from the network, should expect a socket close or error
+ _disconnectExpected = true;
_quitRequested = requested; // see socketDisconnected();
if (!withReconnect) {
_autoReconnectTimer.stop();
_quitReason = reason;
displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting. (%1)").arg((!requested && !withReconnect) ? tr("Core Shutdown") : _quitReason));
- switch (socket.state()) {
- case QAbstractSocket::ConnectedState:
- userInputHandler()->issueQuit(_quitReason);
+ if (socket.state() == QAbstractSocket::UnconnectedState) {
+ socketDisconnected();
+ } else {
+ if (socket.state() == QAbstractSocket::ConnectedState) {
+ userInputHandler()->issueQuit(_quitReason, forceImmediate);
+ } else {
+ socket.close();
+ }
if (requested || withReconnect) {
// the irc server has 10 seconds to close the socket
_socketCloseTimer.start(10000);
- break;
}
- default:
- socket.close();
- socketDisconnected();
}
}
}
-void CoreNetwork::putRawLine(QByteArray s)
+void CoreNetwork::putRawLine(const QByteArray s, const bool prepend)
{
- if (_tokenBucket > 0)
+ if (_tokenBucket > 0) {
writeToSocket(s);
- else
- _msgQueue.append(s);
+ } else {
+ if (prepend) {
+ _msgQueue.prepend(s);
+ } else {
+ _msgQueue.append(s);
+ }
+ }
}
-void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> ¶ms, const QByteArray &prefix)
+void CoreNetwork::putCmd(const QString &cmd, const QList<QByteArray> ¶ms, const QByteArray &prefix, const bool prepend)
{
QByteArray msg;
msg += params[i];
}
- putRawLine(msg);
+ putRawLine(msg, prepend);
+}
+
+
+void CoreNetwork::putCmd(const QString &cmd, const QList<QList<QByteArray>> ¶ms, const QByteArray &prefix, const bool prependAll)
+{
+ QListIterator<QList<QByteArray>> i(params);
+ while (i.hasNext()) {
+ QList<QByteArray> msg = i.next();
+ putCmd(cmd, msg, prefix, prependAll);
+ }
}
else if (s.endsWith("\n"))
s.chop(1);
NetworkDataEvent *event = new NetworkDataEvent(EventManager::NetworkIncoming, this, s);
-#if QT_VERSION >= 0x040700
event->setTimestamp(QDateTime::currentDateTimeUtc());
-#else
- event->setTimestamp(QDateTime::currentDateTime().toUTC());
-#endif
emit newEvent(event);
}
}
void CoreNetwork::socketError(QAbstractSocket::SocketError error)
{
- if (_quitRequested && error == QAbstractSocket::RemoteHostClosedError)
+ // Ignore socket closed errors if expected
+ if (_disconnectExpected && error == QAbstractSocket::RemoteHostClosedError) {
return;
+ }
_previousConnectionAttemptFailed = true;
qWarning() << qPrintable(tr("Could not connect to %1 (%2)").arg(networkName(), socket.errorString()));
disconnectFromIrc();
return;
}
-
- emit socketOpen(identity, localAddress(), localPort(), peerAddress(), peerPort());
-
+
Server server = usedServer();
+
#ifdef HAVE_SSL
- if (server.useSsl && !socket.isEncrypted())
+ // Non-SSL connections enter here only once, always emit socketInitialized(...) in these cases
+ // SSL connections call socketInitialized() twice, only emit socketInitialized(...) on the first (not yet encrypted) run
+ if (!server.useSsl || !socket.isEncrypted()) {
+ emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
+ }
+
+ if (server.useSsl && !socket.isEncrypted()) {
+ // We'll finish setup once we're encrypted, and called again
return;
-#endif
-#if QT_VERSION >= 0x040600
- socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
+ }
+#else
+ emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
#endif
- emit socketInitialized(identity, localAddress(), localPort(), peerAddress(), peerPort());
+ socket.setSocketOption(QAbstractSocket::KeepAliveOption, true);
// TokenBucket to avoid sending too much at once
_messageDelay = 2200; // this seems to be a safe value (2.2 seconds delay)
else {
nick = identity->nicks()[0];
}
- putRawLine(serverEncode(QString("NICK :%1").arg(nick)));
+ putRawLine(serverEncode(QString("NICK %1").arg(nick)));
putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
}
setConnected(false);
emit disconnected(networkId());
emit socketDisconnected(identityPtr(), localAddress(), localPort(), peerAddress(), peerPort());
+ // Reset disconnect expectations
+ _disconnectExpected = false;
if (_quitRequested) {
_quitRequested = false;
setConnectionState(Network::Disconnected);
switch (socketState) {
case QAbstractSocket::UnconnectedState:
state = Network::Disconnected;
+ socketDisconnected();
break;
case QAbstractSocket::HostLookupState:
case QAbstractSocket::ConnectingState:
{
setConnectionState(Network::Initialized);
setConnected(true);
+ _disconnectExpected = false;
_quitRequested = false;
if (useAutoReconnect()) {
}
}
}
+
+
+QList<QList<QByteArray>> CoreNetwork::splitMessage(const QString &cmd, const QString &message, std::function<QList<QByteArray>(QString &)> cmdGenerator)
+{
+ QString wrkMsg(message);
+ QList<QList<QByteArray>> msgsToSend;
+
+ // do while (wrkMsg.size() > 0)
+ do {
+ // First, check to see if the whole message can be sent at once. The
+ // cmdGenerator function is passed in by the caller and is used to encode
+ // and encrypt (if applicable) the message, since different callers might
+ // want to use different encoding or encode different values.
+ int splitPos = wrkMsg.size();
+ QList<QByteArray> initialSplitMsgEnc = cmdGenerator(wrkMsg);
+ int initialOverrun = userInputHandler()->lastParamOverrun(cmd, initialSplitMsgEnc);
+
+ if (initialOverrun) {
+ // If the message was too long to be sent, first try splitting it along
+ // word boundaries with QTextBoundaryFinder.
+ QString splitMsg(wrkMsg);
+ QTextBoundaryFinder qtbf(QTextBoundaryFinder::Word, splitMsg);
+ qtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
+ QList<QByteArray> splitMsgEnc;
+ int overrun = initialOverrun;
+
+ while (overrun) {
+ splitPos = qtbf.toPreviousBoundary();
+
+ // splitPos==-1 means the QTBF couldn't find a split point at all and
+ // splitPos==0 means the QTBF could only find a boundary at the beginning of
+ // the string. Neither one of these works for us.
+ if (splitPos > 0) {
+ // If a split point could be found, split the message there, calculate the
+ // overrun, and continue with the loop.
+ splitMsg = splitMsg.left(splitPos);
+ splitMsgEnc = cmdGenerator(splitMsg);
+ overrun = userInputHandler()->lastParamOverrun(cmd, splitMsgEnc);
+ }
+ else {
+ // If a split point could not be found (the beginning of the message
+ // is reached without finding a split point short enough to send) and we
+ // are still in Word mode, switch to Grapheme mode. We also need to restore
+ // the full wrkMsg to splitMsg, since splitMsg may have been cut down during
+ // the previous attempt to find a split point.
+ if (qtbf.type() == QTextBoundaryFinder::Word) {
+ splitMsg = wrkMsg;
+ splitPos = splitMsg.size();
+ QTextBoundaryFinder graphemeQtbf(QTextBoundaryFinder::Grapheme, splitMsg);
+ graphemeQtbf.setPosition(initialSplitMsgEnc[1].size() - initialOverrun);
+ qtbf = graphemeQtbf;
+ }
+ else {
+ // If the QTBF fails to find a split point in Grapheme mode, we give up.
+ // This should never happen, but it should be handled anyway.
+ qWarning() << "Unexpected failure to split message!";
+ return msgsToSend;
+ }
+ }
+ }
+
+ // Once a message of sendable length has been found, remove it from the wrkMsg and
+ // add it to the list of messages to be sent.
+ wrkMsg.remove(0, splitPos);
+ msgsToSend.append(splitMsgEnc);
+ }
+ else{
+ // If the entire remaining message is short enough to be sent all at once, remove
+ // it from the wrkMsg and add it to the list of messages to be sent.
+ wrkMsg.remove(0, splitPos);
+ msgsToSend.append(initialSplitMsgEnc);
+ }
+ } while (wrkMsg.size() > 0);
+
+ return msgsToSend;
+}