Move alias handling into the client
authorManuel Nickschas <sputnick@quassel-irc.org>
Mon, 2 Mar 2009 07:50:49 +0000 (08:50 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Fri, 13 Mar 2009 19:03:06 +0000 (20:03 +0100)
This allows for aliasing client-side commands such as the soon-to-exist
/exec command.
Note that the core-side code is still there, to be removed as soon as we
break protocol again.

src/client/clientuserinputhandler.cpp
src/client/clientuserinputhandler.h
src/core/userinputhandler.cpp

index 296fc1c..bb6e3ce 100644 (file)
 #include "ircuser.h"
 #include "network.h"
 
-ClientUserInputHandler::ClientUserInputHandler(QObject *parent) : QObject(parent) {
+ClientUserInputHandler::ClientUserInputHandler(QObject *parent)
+: QObject(parent),
+  _initialized(false)
+{
   TabCompletionSettings s;
   s.notify("CompletionSuffix", this, SLOT(completionSuffixChanged(QVariant)));
   completionSuffixChanged(s.completionSuffix());
+
+  // we need this signal for future connects to reset the data;
+  connect(Client::instance(), SIGNAL(connected()), SLOT(clientConnected()));
+  connect(Client::instance(), SIGNAL(disconnected()), SLOT(clientDisconnected()));
+  if(Client::isConnected())
+    clientConnected();
+}
+
+void ClientUserInputHandler::clientConnected() {
+  _aliasManager = AliasManager();
+  Client::signalProxy()->synchronize(&_aliasManager);
+  connect(&_aliasManager, SIGNAL(initDone()), SLOT(initDone()));
+}
+
+void ClientUserInputHandler::clientDisconnected() {
+  // clear alias manager
+  _aliasManager = AliasManager();
+  _initialized = false;
+}
+
+void ClientUserInputHandler::initDone() {
+  _initialized = true;
+  for(int i = 0; i < _inputBuffer.count(); i++)
+    handleUserInput(_inputBuffer.at(i).first, _inputBuffer.at(i).second);
+  _inputBuffer.clear();
 }
 
 void ClientUserInputHandler::completionSuffixChanged(const QVariant &v) {
@@ -40,17 +68,100 @@ void ClientUserInputHandler::completionSuffixChanged(const QVariant &v) {
 }
 
 // this would be the place for a client-side hook
-void ClientUserInputHandler::handleUserInput(const BufferInfo &bufferInfo, const QString &msg) {
-  // check if we addressed a user and update its timestamp in that case
-  if(bufferInfo.type() == BufferInfo::ChannelBuffer) {
-    if(!msg.startsWith('/')) {
-      if(_nickRx.indexIn(msg) == 0) {
-        const Network *net = Client::network(bufferInfo.networkId());
-        IrcUser *user = net ? net->ircUser(_nickRx.cap(1)) : 0;
-        if(user)
-          user->setLastSpokenTo(bufferInfo.bufferId(), QDateTime::currentDateTime().toUTC());
+void ClientUserInputHandler::handleUserInput(const BufferInfo &bufferInfo, const QString &msg_) {
+  QString msg = msg_;
+
+  if(!_initialized) { // aliases not yet synced
+    _inputBuffer.append(qMakePair(bufferInfo, msg));
+    return;
+  }
+
+  // leading slashes indicate there's a command to call unless there is another one in the first section (like a path /proc/cpuinfo)
+  int secondSlashPos = msg.indexOf('/', 1);
+  int firstSpacePos = msg.indexOf(' ');
+  if(!msg.startsWith('/') || (secondSlashPos != -1 && (secondSlashPos < firstSpacePos || firstSpacePos == -1))) {
+    if(msg.startsWith("//"))
+      msg.remove(0, 1); // //asdf is transformed to /asdf
+
+    // check if we addressed a user and update its timestamp in that case
+    if(bufferInfo.type() == BufferInfo::ChannelBuffer) {
+      if(!msg.startsWith('/')) {
+        if(_nickRx.indexIn(msg) == 0) {
+          const Network *net = Client::network(bufferInfo.networkId());
+          IrcUser *user = net ? net->ircUser(_nickRx.cap(1)) : 0;
+          if(user)
+            user->setLastSpokenTo(bufferInfo.bufferId(), QDateTime::currentDateTime().toUTC());
+        }
+      }
+    }
+    msg.prepend("/SAY ");  // make sure we only send proper commands to the core
+
+  } else {
+    // check for aliases
+    QString cmd = msg.section(' ', 0, 0).remove(0, 1).toUpper();
+    for(int i = 0; i < _aliasManager.count(); i++) {
+      if(_aliasManager[i].name.toLower() == cmd.toLower()) {
+        expand(_aliasManager[i].expansion, bufferInfo, msg.section(' ', 1));
+        return;
       }
     }
   }
+
+  // all clear, send off to core.
   emit sendInput(bufferInfo, msg);
 }
+
+void ClientUserInputHandler::expand(const QString &alias, const BufferInfo &bufferInfo, const QString &msg) {
+  const Network *network = Client::network(bufferInfo.networkId());
+  if(!network) {
+    // FIXME send error as soon as we have a method for that!
+    return;
+  }
+
+  QRegExp paramRangeR("\\$(\\d+)\\.\\.(\\d*)");
+  QStringList commands = alias.split(QRegExp("; ?"));
+  QStringList params = msg.split(' ');
+  QStringList expandedCommands;
+  for(int i = 0; i < commands.count(); i++) {
+    QString command = commands[i];
+
+    // replace ranges like $1..3
+    if(!params.isEmpty()) {
+      int pos;
+      while((pos = paramRangeR.indexIn(command)) != -1) {
+        int start = paramRangeR.cap(1).toInt();
+        bool ok;
+        int end = paramRangeR.cap(2).toInt(&ok);
+        if(!ok) {
+          end = params.count();
+        }
+        if(end < start)
+          command = command.replace(pos, paramRangeR.matchedLength(), QString());
+        else {
+          command = command.replace(pos, paramRangeR.matchedLength(), QStringList(params.mid(start - 1, end - start + 1)).join(" "));
+        }
+      }
+    }
+
+    for(int j = params.count(); j > 0; j--) {
+      IrcUser *ircUser = network->ircUser(params[j - 1]);
+      command = command.replace(QString("$%1:hostname").arg(j), ircUser ? ircUser->host() : QString("*"));
+      command = command.replace(QString("$%1").arg(j), params[j - 1]);
+    }
+    command = command.replace("$0", msg);
+    command = command.replace("$channelname", bufferInfo.bufferName());
+    command = command.replace("$currentnick", network->myNick());
+    expandedCommands << command;
+  }
+
+  while(!expandedCommands.isEmpty()) {
+    QString command;
+    if(expandedCommands[0].trimmed().toLower().startsWith("/wait")) {
+      command = expandedCommands.join("; ");
+      expandedCommands.clear();
+    } else {
+      command = expandedCommands.takeFirst();
+    }
+    handleUserInput(bufferInfo, command);
+  }
+}
index bc1b46c..3b2749f 100644 (file)
@@ -21,6 +21,9 @@
 #ifndef CLIENTUSERINPUTHANDLER_H_
 #define CLIENTUSERINPUTHANDLER_H_
 
+#include <QPair>
+
+#include "aliasmanager.h"
 #include "bufferinfo.h"
 
 class ClientUserInputHandler : public QObject {
@@ -36,10 +39,18 @@ signals:
   void sendInput(const BufferInfo &, const QString &);
 
 private slots:
+  void clientConnected();
+  void clientDisconnected();
+  void initDone();
   void completionSuffixChanged(const QVariant &);
 
 private:
+  void expand(const QString &alias, const BufferInfo &bufferInfo, const QString &msg);
+
+  bool _initialized;
   QRegExp _nickRx;
+  AliasManager _aliasManager;
+  QList<QPair<BufferInfo, QString> > _inputBuffer;
 };
 
 #endif
index 8fe87b0..87d37df 100644 (file)
@@ -36,6 +36,8 @@ UserInputHandler::UserInputHandler(CoreNetwork *parent)
 void UserInputHandler::handleUserInput(const BufferInfo &bufferInfo, const QString &msg_) {
   if(msg_.isEmpty())
     return;
+
+  // FIXME: With protocol >= v10, this is all done in the client -> remove
   QString cmd;
   QString msg = msg_;
   // leading slashes indicate there's a command to call unless there is another one in the first section (like a path /proc/cpuinfo)
@@ -391,6 +393,7 @@ void UserInputHandler::handleWhowas(const BufferInfo &bufferInfo, const QString
   emit putCmd("WHOWAS", serverEncode(msg.split(' ')));
 }
 
+// FIXME: Remove alias handling as soon as core protocol >= v10
 void UserInputHandler::defaultHandler(QString cmd, const BufferInfo &bufferInfo, const QString &msg) {
   for(int i = 0; i < coreSession()->aliasManager().count(); i++) {
     if(coreSession()->aliasManager()[i].name.toLower() == cmd.toLower()) {