--- 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 }
};