implement NICK/QUIT handling for irc_net/irc_chan
authorTero Marttila <terom@fixme.fi>
Thu, 26 Mar 2009 23:59:04 +0200
changeset 78 941bb8379d3d
parent 77 5478ade62546
child 79 e26f972300e4
implement NICK/QUIT handling for irc_net/irc_chan
src/irc_chan.c
src/irc_chan.h
src/irc_net.c
src/irc_user.c
src/irc_user.h
src/test.c
--- a/src/irc_chan.c	Thu Mar 26 23:37:31 2009 +0200
+++ b/src/irc_chan.c	Thu Mar 26 23:59:04 2009 +0200
@@ -184,14 +184,38 @@
 }
 
 /**
+ * :nm QUIT [<message>]
+ *
+ * User quit, so remove them from our users list
+ */
+static void irc_chan_on_QUIT (const struct irc_line *line, void *arg)
+{
+    struct irc_chan *chan = arg;
+    struct irc_chan_user *chan_user;
+
+    const char *msg = line->args[1];
+
+    // invoke callback (source, msg)
+    IRC_CHAN_INVOKE_CALLBACK(chan, on_quit, line->source, msg);
+
+    // look up the irc_chan_user
+    if ((chan_user = irc_chan_get_user(chan, line->source->nickname)) == NULL)
+        return log_warn("QUIT'd user not on channel: %s, %s", irc_chan_name(chan), line->source->nickname);
+
+    // remove them
+    irc_chan_remove_user(chan, chan_user);
+}
+
+/**
  * Core command handlers
  */
 struct irc_cmd_handler _cmd_handlers[] = {
     {   "JOIN",             &irc_chan_on_JOIN           },
-    {   "PART",             &irc_chan_on_PART           },
-    {   "PRIVMSG",          &irc_chan_on_PRIVMSG        },
     {   IRC_RPL_NAMREPLY,   &irc_chan_on_RPL_NAMREPLY   },
     {   IRC_RPL_ENDOFNAMES, &irc_chan_on_RPL_ENDOFNAMES },
+    {   "PRIVMSG",          &irc_chan_on_PRIVMSG        },
+    {   "PART",             &irc_chan_on_PART           },
+    {   "QUIT",             &irc_chan_on_QUIT           },
     {   NULL,       NULL                                }
 };
 
--- a/src/irc_chan.h	Thu Mar 26 23:37:31 2009 +0200
+++ b/src/irc_chan.h	Thu Mar 26 23:59:04 2009 +0200
@@ -43,6 +43,9 @@
     /** Someone parted the channel. The irc_chan_user is still present */
     void (*on_part) (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg);
 
+    /** Someone quit the channel. The irc_chan_user is still present */
+    void (*on_quit) (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg);
+
     /** Someone sent a message to the channel */
     void (*on_msg) (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg);
 };
--- a/src/irc_net.c	Thu Mar 26 23:37:31 2009 +0200
+++ b/src/irc_net.c	Thu Mar 26 23:59:04 2009 +0200
@@ -33,6 +33,26 @@
 }
 
 /**
+ * Do a lookup for an irc_user with the given nickname, and return it it found, else NULL.
+ *
+ * This does not refcount anything.
+ */
+static struct irc_user* irc_net_lookup_user (struct irc_net *net, const char *nickname)
+{
+    struct irc_user *user = NULL;
+
+    // look for it...
+    LIST_FOREACH(user, &net->users, net_users) {
+        if (irc_cmp_nick(nickname, user->nickname) == 0)
+            // found existing
+            return user;
+    }
+    
+    // not found
+    return NULL;
+}
+
+/**
  * Our irc_conn has registered. Join our channels
  */
 static void irc_net_conn_registered (struct irc_conn *conn, void *arg)
@@ -134,6 +154,50 @@
 }
 
 /**
+ * Propagate line to irc_chans based on the source nickname and chan_users.
+ */
+static void irc_net_on_chanuser (const struct irc_line *line, void *arg)
+{
+    struct irc_net *net = arg;
+    struct irc_chan *chan;
+    
+    // scan each channel
+    TAILQ_FOREACH(chan, &net->channels, node) {
+        if (irc_chan_get_user(chan, line->source->nickname)) {
+            // propagate
+            irc_cmd_invoke(&chan->handlers, line);
+        }
+    }
+}
+
+/**
+ * :nm NICK <nickname>
+ *
+ * Look for a relevant irc_user, and rename them.
+ *
+ * Ignores if the irc_user could not be found, then propagates to the irc_chan's that the user is on, and then renames
+ * the irc_user.
+ */
+static void irc_net_on_NICK (const struct irc_line *line, void *arg)
+{
+    struct irc_net *net = arg;
+    struct irc_user *user;
+    err_t err;
+
+    // ignore for unknown
+    if ((user = irc_net_lookup_user(net, line->source->nickname)) == NULL)
+        return;
+
+    // propagate to channels
+    irc_net_on_chanuser(line, arg);
+
+    // rename them
+    if ((err = irc_user_rename(user, line->args[0])))
+        // XXX: what to do?
+        log_err(err, "irc_user_rename failed");
+}
+
+/**
  * :nm (PRIVMSG|NOTICE) <target> <message>
  *
  * Either propagate to channel if found, otherwise handle as a privmsg
@@ -157,21 +221,22 @@
  * Our irc_cmd handler list
  */
 static struct irc_cmd_handler _cmd_handlers[] = {
+    // propagate certain messages to the appropriate channel
+    {   "QUIT",             &irc_net_on_chanuser    },
     {   "JOIN",             &irc_net_on_chan0       },
     {   "PART",             &irc_net_on_chan0       },
     {   "MODE",             &irc_net_on_chan0       },
     {   "TOPIC",            &irc_net_on_chan0       },
     {   "KICK",             &irc_net_on_chan0       },
-
+    {   IRC_RPL_NAMREPLY,   &irc_net_on_chan2       },
+    {   IRC_RPL_ENDOFNAMES, &irc_net_on_chan1       },
+    
+    // special-case handling for others
+    {   "NICK",             &irc_net_on_NICK        },
+    // Note: no need to handle QUIT, as the channels should take care of irc_net_put_user'ing it away.
     {   "PRIVMSG",          &irc_net_on_msg         },
     {   "NOTICE",           &irc_net_on_msg         },
-
-    // XXX: NICK/QUIT
-
-    // numerics
-    {   IRC_RPL_NAMREPLY,   &irc_net_on_chan2       },
-    {   IRC_RPL_ENDOFNAMES, &irc_net_on_chan1       },
-
+    
     {   NULL,               NULL                    }
 };
 
@@ -304,19 +369,11 @@
 
 err_t irc_net_get_user (struct irc_net *net, struct irc_user **user_ptr, const char *nickname)
 {
-    struct irc_user *user_iter = NULL, *user = NULL;
+    struct irc_user *user;
     err_t err;
-
-    // look for it...
-    LIST_FOREACH(user_iter, &net->users, net_users) {
-        if (irc_cmp_nick(nickname, user_iter->nickname) == 0) {
-            // found existing
-            user = user_iter;
-            break;
-        }
-    }
     
-    if (!user) {
+    // lookup
+    if ((user = irc_net_lookup_user(net, nickname)) == NULL) {
         // create a new user struct
         if ((err = irc_user_create(&user, net, nickname)))
             return err;
--- a/src/irc_user.c	Thu Mar 26 23:37:31 2009 +0200
+++ b/src/irc_user.c	Thu Mar 26 23:59:04 2009 +0200
@@ -30,6 +30,19 @@
     return SUCCESS;
 }
 
+err_t irc_user_rename (struct irc_user *user, const char *new_nickname)
+{
+    // release old name
+    free(user->nickname);
+    
+    // copy new one
+    if ((user->nickname = strdup(new_nickname)) == NULL)
+        return ERR_STRDUP;
+
+    // ok
+    return SUCCESS;
+}
+
 void irc_user_destroy (struct irc_user *user)
 {
     if (user->refcount > 0)
--- a/src/irc_user.h	Thu Mar 26 23:37:31 2009 +0200
+++ b/src/irc_user.h	Thu Mar 26 23:59:04 2009 +0200
@@ -35,6 +35,11 @@
 err_t irc_user_create (struct irc_user **user_ptr, struct irc_net *net, const char *nickname);
 
 /**
+ * Rename the irc_user with a new nickname
+ */
+err_t irc_user_rename (struct irc_user *user, const char *new_nickname);
+
+/**
  * Destroy an irc_user, releasing memory
  */
 void irc_user_destroy (struct irc_user *user);
--- a/src/test.c	Thu Mar 26 23:37:31 2009 +0200
+++ b/src/test.c	Thu Mar 26 23:59:04 2009 +0200
@@ -876,6 +876,33 @@
     irc_net_destroy(net);
 }
 
+void test_irc_chan_user_nick (void)
+{
+    struct test_chan_ctx ctx;
+    struct sock_test *sock = setup_sock_test();
+    struct irc_net *net = setup_irc_net(sock);
+    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
+
+    // rename one of the users
+    log_info("test irc_net_on_chanuser");
+    test_sock_push(sock, ":userA!someone@somewhere NICK userA2\r\n");
+    check_chan_user(chan, "userA", false);
+    check_chan_user(chan, "userB", true);
+}
+
+void test_irc_chan_user_quit (void)
+{
+    struct test_chan_ctx ctx;
+    struct sock_test *sock = setup_sock_test();
+    struct irc_net *net = setup_irc_net(sock);
+    struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx);
+
+    // rename one of the users
+    log_info("test irc_net_on_chanuser");
+    test_sock_push(sock, ":userA!someone@somewhere QUIT foo\r\n");
+    check_chan_user(chan, "userA", false);
+}
+
 /**
  * Test definition
  */
@@ -898,6 +925,8 @@
     {   "irc_chan_namreply",    &test_irc_chan_namreply     },
     {   "irc_chan_user_join",   &test_irc_chan_user_join    },
     {   "irc_chan_user_part",   &test_irc_chan_user_part    },
+    {   "irc_chan_user_nick",   &test_irc_chan_user_nick    },
+    {   "irc_chan_user_quit",   &test_irc_chan_user_quit    },
     {   NULL,                   NULL                        }
 };