Use late CoreSessionEventProcessor handler rather than early EventStringifier ones
[quassel.git] / src / core / coresessioneventprocessor.cpp
index 3b5ad27..7d5d244 100644 (file)
 #include "coresession.h"
 #include "ircevent.h"
 #include "ircuser.h"
+#include "messageevent.h"
+#include "netsplit.h"
 
 CoreSessionEventProcessor::CoreSessionEventProcessor(CoreSession *session)
   : QObject(session),
   _coreSession(session)
 {
-
+  connect(coreSession(), SIGNAL(networkDisconnected(NetworkId)), this, SLOT(destroyNetsplits(NetworkId)));
 }
 
 bool CoreSessionEventProcessor::checkParamCount(IrcEvent *e, int minParams) {
@@ -47,6 +49,28 @@ bool CoreSessionEventProcessor::checkParamCount(IrcEvent *e, int minParams) {
   return true;
 }
 
+void CoreSessionEventProcessor::tryNextNick(NetworkEvent *e, const QString &errnick, bool erroneus) {
+  QStringList desiredNicks = coreSession()->identity(e->network()->identity())->nicks();
+  int nextNickIdx = desiredNicks.indexOf(errnick) + 1;
+  QString nextNick;
+  if(nextNickIdx > 0 && desiredNicks.size() > nextNickIdx) {
+    nextNick = desiredNicks[nextNickIdx];
+  } else {
+    if(erroneus) {
+      // FIXME Make this an ErrorEvent or something like that, so it's translated in the client
+      MessageEvent *msgEvent = new MessageEvent(Message::Error, e->network(),
+                                                tr("No free and valid nicks in nicklist found. use: /nick <othernick> to continue"),
+                                                QString(), QString(), Message::None, e->timestamp());
+      coreSession()->eventManager()->sendEvent(msgEvent);
+      return;
+    } else {
+      nextNick = errnick + "_";
+    }
+  }
+  // FIXME Use a proper output event for this
+  coreNetwork(e)->putRawLine("NICK " + coreNetwork(e)->encodeServerString(nextNick));
+}
+
 void CoreSessionEventProcessor::processIrcEventNumeric(IrcEventNumeric *e) {
   switch(e->number()) {
 
@@ -99,7 +123,37 @@ void CoreSessionEventProcessor::processIrcEventInvite(IrcEvent *e) {
   }
 }
 
-void CoreSessionEventProcessor::processIrcEventKick(IrcEvent *e) {
+void CoreSessionEventProcessor::processIrcEventJoin(IrcEvent *e) {
+  if(e->testFlag(EventManager::Fake)) // generated by handleEarlyNetsplitJoin
+    return;
+
+  if(!checkParamCount(e, 1))
+    return;
+
+  CoreNetwork *net = coreNetwork(e);
+  QString channel = e->params()[0];
+  IrcUser *ircuser = net->updateNickFromMask(e->prefix());
+
+  bool handledByNetsplit = false;
+  foreach(Netsplit* n, _netsplits.value(e->network())) {
+    handledByNetsplit = n->userJoined(e->prefix(), channel);
+    if(handledByNetsplit)
+      break;
+  }
+
+  if(!handledByNetsplit)
+    ircuser->joinChannel(channel);
+  else
+    e->setFlag(EventManager::Netsplit);
+
+  if(net->isMe(ircuser)) {
+    net->setChannelJoined(channel);
+     // FIXME use event
+    net->putRawLine(net->serverEncode("MODE " + channel)); // we want to know the modes of the channel we just joined, so we ask politely
+  }
+}
+
+void CoreSessionEventProcessor::lateProcessIrcEventKick(IrcEvent *e) {
   if(checkParamCount(e, 2)) {
     e->network()->updateNickFromMask(e->prefix());
     IrcUser *victim = e->network()->ircUser(e->params().at(1));
@@ -110,7 +164,113 @@ void CoreSessionEventProcessor::processIrcEventKick(IrcEvent *e) {
   }
 }
 
-void CoreSessionEventProcessor::processIrcEventNick(IrcEvent *e) {
+void CoreSessionEventProcessor::processIrcEventMode(IrcEvent *e) {
+  if(!checkParamCount(e, 2))
+    return;
+
+  if(e->network()->isChannelName(e->params().first())) {
+    // Channel Modes
+
+    IrcChannel *channel = e->network()->ircChannel(e->params()[0]);
+    if(!channel) {
+      // we received mode information for a channel we're not in. that means probably we've just been kicked out or something like that
+      // anyways: we don't have a place to store the data --> discard the info.
+      return;
+    }
+
+    QString modes = e->params()[1];
+    bool add = true;
+    int paramOffset = 2;
+    for(int c = 0; c < modes.length(); c++) {
+      if(modes[c] == '+') {
+        add = true;
+        continue;
+      }
+      if(modes[c] == '-') {
+        add = false;
+        continue;
+      }
+
+      if(e->network()->prefixModes().contains(modes[c])) {
+        // user channel modes (op, voice, etc...)
+        if(paramOffset < e->params().count()) {
+          IrcUser *ircUser = e->network()->ircUser(e->params()[paramOffset]);
+          if(!ircUser) {
+            qWarning() << Q_FUNC_INFO << "Unknown IrcUser:" << e->params()[paramOffset];
+          } else {
+            if(add) {
+              bool handledByNetsplit = false;
+              QHash<QString, Netsplit *> splits = _netsplits.value(e->network());
+              foreach(Netsplit* n, _netsplits.value(e->network())) {
+                handledByNetsplit = n->userAlreadyJoined(ircUser->hostmask(), channel->name());
+                if(handledByNetsplit) {
+                  n->addMode(ircUser->hostmask(), channel->name(), QString(modes[c]));
+                  break;
+                }
+              }
+              if(!handledByNetsplit)
+                channel->addUserMode(ircUser, QString(modes[c]));
+            }
+            else
+              channel->removeUserMode(ircUser, QString(modes[c]));
+          }
+        } else {
+          qWarning() << "Received MODE with too few parameters:" << e->params();
+        }
+        ++paramOffset;
+      } else {
+        // regular channel modes
+        QString value;
+        Network::ChannelModeType modeType = e->network()->channelModeType(modes[c]);
+        if(modeType == Network::A_CHANMODE || modeType == Network::B_CHANMODE || (modeType == Network::C_CHANMODE && add)) {
+          if(paramOffset < e->params().count()) {
+            value = e->params()[paramOffset];
+          } else {
+            qWarning() << "Received MODE with too few parameters:" << e->params();
+          }
+          ++paramOffset;
+        }
+
+        if(add)
+          channel->addChannelMode(modes[c], value);
+        else
+          channel->removeChannelMode(modes[c], value);
+      }
+    }
+
+  } else {
+    // pure User Modes
+    IrcUser *ircUser = e->network()->newIrcUser(e->params().first());
+    QString modeString(e->params()[1]);
+    QString addModes;
+    QString removeModes;
+    bool add = false;
+    for(int c = 0; c < modeString.count(); c++) {
+      if(modeString[c] == '+') {
+        add = true;
+        continue;
+      }
+      if(modeString[c] == '-') {
+        add = false;
+        continue;
+      }
+      if(add)
+        addModes += modeString[c];
+      else
+        removeModes += modeString[c];
+    }
+    if(!addModes.isEmpty())
+      ircUser->addUserModes(addModes);
+    if(!removeModes.isEmpty())
+      ircUser->removeUserModes(removeModes);
+
+    if(e->network()->isMe(ircUser)) {
+      coreNetwork(e)->updatePersistentModes(addModes, removeModes);
+    }
+  }
+}
+
+void CoreSessionEventProcessor::lateProcessIrcEventNick(IrcEvent *e) {
   if(checkParamCount(e, 1)) {
     IrcUser *ircuser = e->network()->updateNickFromMask(e->prefix());
     if(!ircuser) {
@@ -128,7 +288,7 @@ void CoreSessionEventProcessor::processIrcEventNick(IrcEvent *e) {
   }
 }
 
-void CoreSessionEventProcessor::processIrcEventPart(IrcEvent *e) {
+void CoreSessionEventProcessor::lateProcessIrcEventPart(IrcEvent *e) {
   if(checkParamCount(e, 1)) {
     IrcUser *ircuser = e->network()->updateNickFromMask(e->prefix());
     if(!ircuser) {
@@ -142,6 +302,12 @@ void CoreSessionEventProcessor::processIrcEventPart(IrcEvent *e) {
   }
 }
 
+void CoreSessionEventProcessor::processIrcEventPing(IrcEvent *e) {
+  QString param = e->params().count()? e->params().first() : QString();
+  // FIXME use events
+  coreNetwork(e)->putRawLine("PONG " + coreNetwork(e)->serverEncode(param));
+}
+
 void CoreSessionEventProcessor::processIrcEventPong(IrcEvent *e) {
   // the server is supposed to send back what we passed as param. and we send a timestamp
   // but using quote and whatnought one can send arbitrary pings, so we have to do some sanity checks
@@ -153,6 +319,50 @@ void CoreSessionEventProcessor::processIrcEventPong(IrcEvent *e) {
   }
 }
 
+void CoreSessionEventProcessor::processIrcEventQuit(IrcEvent *e) {
+  IrcUser *ircuser = e->network()->updateNickFromMask(e->prefix());
+  if(!ircuser)
+    return;
+
+  QString msg;
+  if(e->params().count() > 0)
+    msg = e->params()[0];
+
+  // check if netsplit
+  if(Netsplit::isNetsplit(msg)) {
+    Netsplit *n;
+    if(!_netsplits[e->network()].contains(msg)) {
+      n = new Netsplit(e->network(), this);
+      connect(n, SIGNAL(finished()), this, SLOT(handleNetsplitFinished()));
+      connect(n, SIGNAL(netsplitJoin(Network*,QString,QStringList,QStringList,QString)),
+              this, SLOT(handleNetsplitJoin(Network*,QString,QStringList,QStringList,QString)));
+      connect(n, SIGNAL(netsplitQuit(Network*,QString,QStringList,QString)),
+              this, SLOT(handleNetsplitQuit(Network*,QString,QStringList,QString)));
+      connect(n, SIGNAL(earlyJoin(Network*,QString,QStringList,QStringList)),
+              this, SLOT(handleEarlyNetsplitJoin(Network*,QString,QStringList,QStringList)));
+      _netsplits[e->network()].insert(msg, n);
+    }
+    else {
+      n = _netsplits[e->network()][msg];
+    }
+    // add this user to the netsplit
+    n->userQuit(e->prefix(), ircuser->channels(), msg);
+    e->setFlag(EventManager::Netsplit);
+  }
+  // normal quit is handled in lateProcessIrcEventQuit()
+}
+
+void CoreSessionEventProcessor::lateProcessIrcEventQuit(IrcEvent *e) {
+  if(e->testFlag(EventManager::Netsplit))
+    return;
+
+  IrcUser *ircuser = e->network()->updateNickFromMask(e->prefix());
+  if(!ircuser)
+    return;
+
+  ircuser->quit();
+}
+
 void CoreSessionEventProcessor::processIrcEventTopic(IrcEvent *e) {
   if(checkParamCount(e, 2)) {
     e->network()->updateNickFromMask(e->prefix());
@@ -370,6 +580,11 @@ void CoreSessionEventProcessor::processIrcEvent323(IrcEvent *e) {
     e->stop(); // consumed by IrcListHelper, so don't further process/show this event
 }
 
+/* RPL_CHANNELMODEIS - "<channel> <mode> <mode params>" */
+void CoreSessionEventProcessor::processIrcEvent324(IrcEvent *e) {
+  processIrcEventMode(e);
+}
+
 /* RPL_NOTOPIC */
 void CoreSessionEventProcessor::processIrcEvent331(IrcEvent *e) {
   if(!checkParamCount(e, 1))
@@ -445,6 +660,54 @@ void CoreSessionEventProcessor::processIrcEvent353(IrcEvent *e) {
   channel->joinIrcUsers(nicks, modes);
 }
 
+/* ERR_ERRONEUSNICKNAME */
+void CoreSessionEventProcessor::processIrcEvent432(IrcEventNumeric *e) {
+  QString errnick;
+  if(e->params().count() < 2) {
+    // handle unreal-ircd bug, where unreal ircd doesnt supply a TARGET in ERR_ERRONEUSNICKNAME during registration phase:
+    // nick @@@
+    // :irc.scortum.moep.net 432  @@@ :Erroneous Nickname: Illegal characters
+    // correct server reply:
+    // :irc.scortum.moep.net 432 * @@@ :Erroneous Nickname: Illegal characters
+    e->params().prepend(e->target());
+    e->setTarget("*");
+  }
+  errnick = e->params()[0];
+
+  tryNextNick(e, errnick, true /* erroneus */);
+}
+
+/* ERR_NICKNAMEINUSE */
+void CoreSessionEventProcessor::processIrcEvent433(IrcEventNumeric *e) {
+  if(!checkParamCount(e, 1))
+    return;
+
+  QString errnick = e->params().first();
+
+  // if there is a problem while connecting to the server -> we handle it
+  // but only if our connection has not been finished yet...
+  if(!e->network()->currentServer().isEmpty())
+    return;
+
+  tryNextNick(e, errnick);
+}
+
+/* ERR_UNAVAILRESOURCE */
+void CoreSessionEventProcessor::processIrcEvent437(IrcEventNumeric *e) {
+  if(!checkParamCount(e, 1))
+    return;
+
+  QString errnick = e->params().first();
+
+  // if there is a problem while connecting to the server -> we handle it
+  // but only if our connection has not been finished yet...
+  if(!e->network()->currentServer().isEmpty())
+    return;
+
+  if(!e->network()->isChannelName(errnick))
+    tryNextNick(e, errnick);
+}
+
 /* template
 void CoreSessionEventProcessor::processIrcEvent(IrcEvent *e) {
   if(!checkParamCount(e, 1))
@@ -452,3 +715,92 @@ void CoreSessionEventProcessor::processIrcEvent(IrcEvent *e) {
 
 }
 */
+
+/* Handle signals from Netsplit objects  */
+
+void CoreSessionEventProcessor::handleNetsplitJoin(Network *net,
+                                                   const QString &channel,
+                                                   const QStringList &users,
+                                                   const QStringList &modes,
+                                                   const QString& quitMessage)
+{
+  IrcChannel *ircChannel = net->ircChannel(channel);
+  if(!ircChannel) {
+    return;
+  }
+  QList<IrcUser *> ircUsers;
+  QStringList newModes = modes;
+  QStringList newUsers = users;
+
+  foreach(const QString &user, users) {
+    IrcUser *iu = net->ircUser(nickFromMask(user));
+    if(iu)
+      ircUsers.append(iu);
+    else { // the user already quit
+      int idx = users.indexOf(user);
+      newUsers.removeAt(idx);
+      newModes.removeAt(idx);
+    }
+  }
+
+  ircChannel->joinIrcUsers(ircUsers, newModes);
+  NetworkSplitEvent *event = new NetworkSplitEvent(EventManager::NetworkSplitJoin, net, channel, newUsers, quitMessage);
+  coreSession()->eventManager()->sendEvent(event);
+}
+
+void CoreSessionEventProcessor::handleNetsplitQuit(Network *net, const QString &channel, const QStringList &users, const QString& quitMessage) {
+  NetworkSplitEvent *event = new NetworkSplitEvent(EventManager::NetworkSplitQuit, net, channel, users, quitMessage);
+  coreSession()->eventManager()->sendEvent(event);
+  foreach(QString user, users) {
+    IrcUser *iu = net->ircUser(nickFromMask(user));
+    if(iu)
+      iu->quit();
+  }
+}
+
+void CoreSessionEventProcessor::handleEarlyNetsplitJoin(Network *net, const QString &channel, const QStringList &users, const QStringList &modes) {
+  IrcChannel *ircChannel = net->ircChannel(channel);
+  if(!ircChannel) {
+    qDebug() << "handleEarlyNetsplitJoin(): channel " << channel << " invalid";
+    return;
+  }
+  QList<NetworkEvent *> events;
+  QList<IrcUser *> ircUsers;
+  QStringList newModes = modes;
+
+  foreach(QString user, users) {
+    IrcUser *iu = net->updateNickFromMask(user);
+    if(iu) {
+      ircUsers.append(iu);
+      // fake event for scripts that consume join events
+      events << new IrcEvent(EventManager::IrcEventJoin, net, iu->hostmask(), QStringList() << channel);
+    }
+    else {
+      newModes.removeAt(users.indexOf(user));
+    }
+  }
+  ircChannel->joinIrcUsers(ircUsers, newModes);
+  foreach(NetworkEvent *event, events) {
+    event->setFlag(EventManager::Fake); // ignore this in here!
+    coreSession()->eventManager()->sendEvent(event);
+  }
+}
+
+void CoreSessionEventProcessor::handleNetsplitFinished() {
+  Netsplit* n = qobject_cast<Netsplit*>(sender());
+  Q_ASSERT(n);
+  QHash<QString, Netsplit *> splithash  = _netsplits.take(n->network());
+  splithash.remove(splithash.key(n));
+  if(splithash.count())
+    _netsplits[n->network()] = splithash;
+  n->deleteLater();
+}
+
+void CoreSessionEventProcessor::destroyNetsplits(NetworkId netId) {
+  Network *net = coreSession()->network(netId);
+  if(!net)
+    return;
+
+  QHash<QString, Netsplit *> splits = _netsplits.take(net);
+  qDeleteAll(splits);
+}