# HG changeset patch # User Tero Marttila # Date 1238100865 -7200 # Node ID 11ec458d1cbf137c269f819a2014dc32e1a08522 # Parent 2780a73c71f37e99ca0428d2c32cd813d3abf0fd add irc_chan_on_PART, irc_net_put_user and test_irc_chan_user_part diff -r 2780a73c71f3 -r 11ec458d1cbf src/irc_chan.c --- 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 */ 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 [] + */ +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 */ 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); diff -r 2780a73c71f3 -r 11ec458d1cbf src/irc_chan.h --- 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 */ diff -r 2780a73c71f3 -r 11ec458d1cbf src/irc_net.c --- 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 #include +#include 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) diff -r 2780a73c71f3 -r 11ec458d1cbf src/irc_net.h --- 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); diff -r 2780a73c71f3 -r 11ec458d1cbf src/irc_user.c --- 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 #include +#include + +// 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); } diff -r 2780a73c71f3 -r 11ec458d1cbf src/irc_user.h --- 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); diff -r 2780a73c71f3 -r 11ec458d1cbf src/test.c --- 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 } };