--- a/src/irc_chan.c Thu Mar 26 22:03:20 2009 +0200
+++ b/src/irc_chan.c Thu Mar 26 22:54:25 2009 +0200
@@ -27,7 +27,7 @@
if (irc_chan_get_user(chan, nickname))
return SUCCESS;
- // lookup/create the irc_user state
+ // lookup/create the irc_user state, incrementing the refcount
if ((err = irc_net_get_user(chan->net, &user, nickname)))
return err;
@@ -48,6 +48,21 @@
}
/**
+ * Remove an irc_chan_user previously added by irc_chan_add_user().
+ */
+static void irc_chan_remove_user (struct irc_chan *chan, struct irc_chan_user *chan_user)
+{
+ // put the irc_user reference back, decrementing refcount
+ irc_net_put_user(chan->net, chan_user->user);
+
+ // remove from list
+ LIST_REMOVE(chan_user, chan_users);
+
+ // free
+ free(chan_user);
+}
+
+/**
* :nm JOIN <channel>
*/
static void irc_chan_on_JOIN (const struct irc_line *line, void *arg)
@@ -82,6 +97,49 @@
}
/**
+ * Someone, or us ourselves, left a channel
+ *
+ * :nm PART <channel> [<message>]
+ */
+static void irc_chan_on_PART (const struct irc_line *line, void *arg)
+{
+ struct irc_chan *chan = arg;
+ err_t err;
+
+ const char *msg = line->args[1];
+
+ // us?
+ if (irc_prefix_cmp_nick(line->prefix, chan->net->conn->nickname) == 0) {
+ // twiddle state
+ chan->joined = false;
+ chan->parted = true;
+
+ // invoke callback
+ IRC_CHAN_INVOKE_CALLBACK(chan, on_self_part);
+
+ } else {
+ // someone else
+ char prefix_buf[IRC_PREFIX_MAX];
+ struct irc_nm nm;
+ struct irc_chan_user *chan_user;
+
+ // parse the nickname
+ if ((err = irc_nm_parse(&nm, prefix_buf, line->prefix)))
+ return log_warn("invalid prefix: %s", line->prefix);
+
+ // invoke callback (source, msg)
+ IRC_CHAN_INVOKE_CALLBACK(chan, on_part, &nm, msg);
+
+ // look up the irc_chan_user
+ if ((chan_user = irc_chan_get_user(chan, nm.nickname)) == NULL)
+ return log_warn("PART'd user not on channel: %s, %s", irc_chan_name(chan), nm.nickname);
+
+ // remove them
+ irc_chan_remove_user(chan, chan_user);
+ }
+}
+
+/**
* :nm PRIVMSG <channel> <message>
*/
static void irc_chan_on_PRIVMSG (const struct irc_line *line, void *arg)
@@ -156,6 +214,7 @@
*/
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 },
@@ -197,7 +256,12 @@
void irc_chan_destroy (struct irc_chan *chan)
{
- // XXX: free chan_users list
+ struct irc_chan_user *chan_user;
+
+ // free users
+ while ((chan_user = LIST_FIRST(&chan->users))) {
+ irc_chan_remove_user(chan, chan_user);
+ }
// free command handlers
irc_cmd_free(&chan->handlers);
--- a/src/irc_chan.h Thu Mar 26 22:03:20 2009 +0200
+++ b/src/irc_chan.h Thu Mar 26 22:54:25 2009 +0200
@@ -34,9 +34,15 @@
/** Joined the channel */
void (*on_self_join) (struct irc_chan *chan, void *arg);
+ /** We parted the channel */
+ void (*on_self_part) (struct irc_chan *chan, void *arg);
+
/** Someone joined the channel */
void (*on_join) (struct irc_chan *chan, const struct irc_nm *source, void *arg);
+ /** 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 sent a message to the channel */
void (*on_msg) (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg);
};
@@ -91,6 +97,9 @@
/** Currently joined to channel */
bool joined;
+ /** We PART'd the channel */
+ bool parted;
+
// @}
/** List of users on channel */
--- a/src/irc_net.c Thu Mar 26 22:03:20 2009 +0200
+++ b/src/irc_net.c Thu Mar 26 22:54:25 2009 +0200
@@ -3,6 +3,7 @@
#include <stdlib.h>
#include <string.h>
+#include <assert.h>
const char* irc_net_name (struct irc_net *net)
{
@@ -303,25 +304,33 @@
err_t irc_net_get_user (struct irc_net *net, struct irc_user **user_ptr, const char *nickname)
{
- struct irc_user *user = NULL;
+ struct irc_user *user_iter = NULL, *user = NULL;
err_t err;
// look for it...
- LIST_FOREACH(user, &net->users, net_users) {
- if (irc_cmp_nick(nickname, user->nickname) == 0) {
+ LIST_FOREACH(user_iter, &net->users, net_users) {
+ if (irc_cmp_nick(nickname, user_iter->nickname) == 0) {
// found existing
- *user_ptr = user;
-
- return SUCCESS;
+ user = user_iter;
+ break;
}
}
- // create a new user struct
- if ((err = irc_user_create(&user, net, nickname)))
- return err;
+ if (!user) {
+ // create a new user struct
+ if ((err = irc_user_create(&user, net, nickname)))
+ return err;
- // add to our list
- LIST_INSERT_HEAD(&net->users, user, net_users);
+ // add to our list
+ LIST_INSERT_HEAD(&net->users, user, net_users);
+
+ } else {
+ // check for refcount overflow
+ assert(user->refcount < SIZE_MAX);
+ }
+
+ // 'get' it
+ user->refcount++;
// ok
*user_ptr = user;
@@ -329,6 +338,23 @@
return SUCCESS;
}
+void irc_net_put_user (struct irc_net *net, struct irc_user *user)
+{
+ (void) net;
+
+ assert(user->refcount > 0);
+
+ // nothing if it's still in use elsewhere
+ if (--user->refcount)
+ return;
+
+ // remove from list
+ LIST_REMOVE(user, net_users);
+
+ // destroy
+ irc_user_destroy(user);
+}
+
err_t irc_net_quit (struct irc_net *net, const char *message)
{
if (!net->conn)
--- a/src/irc_net.h Thu Mar 26 22:03:20 2009 +0200
+++ b/src/irc_net.h Thu Mar 26 22:54:25 2009 +0200
@@ -103,6 +103,9 @@
/**
* Look up an existing irc_user by nickname, or create a new one if not found.
*
+ * This will increment the reference count on the irc_user object, so do call irc_net_put_user once you're done with
+ * it, or you'll get reference-counting bugs...
+ *
* @param user_ptr set to the new irc_user state
* @param net the network context
* @param nickname the user's current nickname
@@ -110,6 +113,11 @@
err_t irc_net_get_user (struct irc_net *net, struct irc_user **user_ptr, const char *nickname);
/**
+ * Release a previously get'd irc_user, decrementing its refcount and destroying it if unused.
+ */
+void irc_net_put_user (struct irc_net *net, struct irc_user *user);
+
+/**
* Quit from the IRC network, this sends a QUIT message to the server, and waits for the connection to be closed cleanly.
*/
err_t irc_net_quit (struct irc_net *net, const char *message);
--- a/src/irc_user.c Thu Mar 26 22:03:20 2009 +0200
+++ b/src/irc_user.c Thu Mar 26 22:54:25 2009 +0200
@@ -1,19 +1,28 @@
#include "irc_user.h"
+#include "log.h"
#include <stdlib.h>
#include <string.h>
+#include <assert.h>
+
+// XXX: prototype of function from irc_net
+void irc_net_remove_user (struct irc_net *net, struct irc_user *user);
err_t irc_user_create (struct irc_user **user_ptr, struct irc_net *net, const char *nickname)
{
struct irc_user *user;
+
+ (void) net;
// allocate
if ((user = calloc(1, sizeof(*user))) == NULL)
return ERR_CALLOC;
- // copy nickname
+ // init
if ((user->nickname = strdup(nickname)) == NULL)
return ERR_STRDUP;
+
+ user->refcount = 0;
// ok
*user_ptr = user;
@@ -23,6 +32,9 @@
void irc_user_destroy (struct irc_user *user)
{
+ if (user->refcount > 0)
+ log_warn("refcount=%zu", user->refcount);
+
free(user->nickname);
free(user);
}
--- a/src/irc_user.h Thu Mar 26 22:03:20 2009 +0200
+++ b/src/irc_user.h Thu Mar 26 22:54:25 2009 +0200
@@ -20,12 +20,17 @@
/** The user's nickname */
char *nickname;
+ /** Refcount for irc_chan.users lists */
+ size_t refcount;
+
/** Our entry in the irc_net list */
LIST_ENTRY(irc_user) net_users;
};
/**
* Create a new irc_user with the given nickname. The nickname is copied for storage.
+ *
+ * XXX: this is a private function for use by irc_net
*/
err_t irc_user_create (struct irc_user **user_ptr, struct irc_net *net, const char *nickname);
--- a/src/test.c Thu Mar 26 22:03:20 2009 +0200
+++ b/src/test.c Thu Mar 26 22:54:25 2009 +0200
@@ -516,7 +516,7 @@
struct irc_chan *chan;
/** Flags for callbacks called*/
- bool on_chan_self_join, on_chan_join;
+ bool on_chan_self_join, on_chan_self_part, on_chan_join, on_chan_part;
};
@@ -544,9 +544,25 @@
log_debug("on_join");
}
+void _on_chan_part (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg)
+{
+ struct test_chan_ctx *ctx = arg;
+
+ assert(chan == ctx->chan);
+
+ // XXX: verify source
+ // XXX: verify msg
+
+ ctx->on_chan_part = true;
+
+ log_debug("on_part");
+}
+
+
struct irc_chan_callbacks _chan_callbacks = {
.on_self_join = &_on_chan_self_join,
.on_join = &_on_chan_join,
+ .on_part = &_on_chan_part,
};
/**
@@ -624,6 +640,28 @@
}
/**
+ * Ensure that an irc_chan_user exists/doesn't exist for the given channel/nickname, and return it
+ */
+struct irc_chan_user* check_chan_user (struct irc_chan *chan, const char *nickname, bool exists)
+{
+ struct irc_chan_user *chan_user = irc_chan_get_user(chan, nickname);
+
+ if (exists && chan_user == NULL)
+ FATAL("user %s not found in channel %s", dump_str(nickname), dump_str(irc_chan_name(chan)));
+
+ if (!exists && chan_user)
+ FATAL("user %s should not be on channel %s anymore", dump_str(nickname), dump_str(irc_chan_name(chan)));
+
+ log_debug("%s, exists=%d -> %p: user=%p, nickname=%s",
+ nickname, exists, chan_user, chan_user ? chan_user->user : NULL, chan_user ? chan_user->user->nickname : NULL);
+
+ if (chan_user)
+ assert_strcmp(chan_user->user->nickname, nickname);
+
+ return chan_user;
+}
+
+/**
* Creates an irc_chan on the given irc_net, but does not check any output (useful for testing offline add).
*
* You must pass a test_chan_ctx for use with later operations, this will be initialized for you.
@@ -665,9 +703,25 @@
}
/**
+ * Sends a short RPL_NAMREPLY/RPL_ENDOFNAMES reply and checks that the users list matches
+ */
+void do_irc_chan_namreply (struct sock_test *sock, struct test_chan_ctx *ctx)
+{
+ // RPL_NAMREPLY
+ test_sock_push(sock, "353 mynick = #test :mynick userA +userB @userC\r\n");
+ test_sock_push(sock, "366 mynick #test :End of NAMES\r\n");
+
+ // XXX: this should be an exclusive test, i.e. these should be the only ones...
+ check_chan_user(ctx->chan, "mynick", true);
+ check_chan_user(ctx->chan, "userA", true);
+ check_chan_user(ctx->chan, "userB", true);
+ check_chan_user(ctx->chan, "userC", true);
+}
+
+/**
* Creates an irc_chan on the given irc_net, and checks up to the JOIN reply
*/
-struct irc_chan* setup_irc_chan (struct sock_test *sock, struct irc_net *net, struct test_chan_ctx *ctx)
+struct irc_chan* setup_irc_chan_join (struct sock_test *sock, struct irc_net *net, struct test_chan_ctx *ctx)
{
setup_irc_chan_raw(net, ctx);
do_irc_chan_join(sock, ctx);
@@ -677,6 +731,20 @@
}
/**
+ * Creates an irc_chan on the given irc_net, sends the JOIN stuff plus RPL_NAMREPLY
+ */
+struct irc_chan* setup_irc_chan (struct sock_test *sock, struct irc_net *net, struct test_chan_ctx *ctx)
+{
+ setup_irc_chan_raw(net, ctx);
+ do_irc_chan_join(sock, ctx);
+ do_irc_chan_namreply(sock, ctx);
+
+ // ok
+ return ctx->chan;
+}
+
+
+/**
* Call irc_net_add_chan while offline, and ensure that we send the JOIN request after RPL_WELCOME, and handle the join
* reply OK.
*/
@@ -706,38 +774,15 @@
irc_net_destroy(net);
}
-/**
- * Ensure that an irc_chan_user exists for the given channel/nickname, and return it
- */
-struct irc_chan_user* check_chan_user (struct irc_chan *chan, const char *nickname)
-{
- struct irc_chan_user *chan_user;
-
- if ((chan_user = irc_chan_get_user(chan, nickname)) == NULL)
- FATAL("user %s not found in channel %s", dump_str(nickname), dump_str(irc_chan_name(chan)));
-
- log_debug("%s -> %p: user=%p, nickname=%s", nickname, chan_user, chan_user->user, chan_user->user->nickname);
-
- assert_strcmp(chan_user->user->nickname, nickname);
-
- return chan_user;
-}
-
void test_irc_chan_namreply (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, &ctx);
+ setup_irc_chan_join(sock, net, &ctx);
- // RPL_NAMREPLY
log_info("test irc_chan_on_RPL_NAMREPLY");
- test_sock_push(sock, "353 mynick = #test :mynick userA +userB @userC\r\n");
-
- check_chan_user(chan, "mynick");
- check_chan_user(chan, "userA");
- check_chan_user(chan, "userB");
- check_chan_user(chan, "userC");
+ do_irc_chan_namreply(sock, &ctx);
// cleanup
irc_net_destroy(net);
@@ -750,19 +795,28 @@
struct irc_net *net = setup_irc_net(sock);
struct irc_chan *chan = setup_irc_chan(sock, net, &ctx);
- // RPL_NAMREPLY
- test_sock_push(sock, "353 mynick = #test :mynick userA +userB @userC\r\n");
-
- check_chan_user(chan, "mynick");
- check_chan_user(chan, "userA");
- check_chan_user(chan, "userB");
- check_chan_user(chan, "userC");
-
// have a user join
log_info("test irc_chan_on_JOIN");
test_sock_push(sock, ":newuser!someone@somewhere JOIN #test\r\n");
assert(ctx.on_chan_join);
- check_chan_user(chan, "newuser");
+ check_chan_user(chan, "newuser", true);
+
+ // cleanup
+ irc_net_destroy(net);
+}
+
+void test_irc_chan_user_part (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, &ctx);
+
+ // have a user join
+ log_info("test irc_chan_on_PART");
+ test_sock_push(sock, ":userA!someone@somewhere PART #test\r\n");
+ assert(ctx.on_chan_part); ctx.on_chan_part = NULL;
+ check_chan_user(chan, "userA", false);
// cleanup
irc_net_destroy(net);
@@ -787,6 +841,7 @@
{ "irc_chan_add_offline", &test_irc_chan_add_offline },
{ "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 },
{ NULL, NULL }
};