src/irc_chan.c
author Tero Marttila <terom@fixme.fi>
Wed, 27 May 2009 23:57:48 +0300
branchnew-lib-errors
changeset 217 7728d6ec3abf
parent 197 3f9175ead92a
permissions -rw-r--r--
nexus.c compiles
#include "irc_chan.h"
#include "irc_proto.h"
#include "log.h"

#include <stdlib.h>
#include <string.h>
#include <assert.h>

const char* irc_chan_name (struct irc_chan *chan)
{
    return chan ? chan->info.channel : "NULL";
}

/**
 * Invoke the named irc_chan_callbacks field with the given args
 */
#define IRC_CHAN_INVOKE_CALLBACK(chan, _cb_name_, ...)          \
    do {                                                        \
        struct irc_chan_callback_item *item;                    \
                                                                \
        CHAIN_FOREACH_SAFE(&(chan)->callbacks, item) {          \
            if (item->callbacks->_cb_name_)                     \
                item->callbacks->_cb_name_((chan), ## __VA_ARGS__, item->arg);  \
        }                                                       \
    } while (0)

/**
 * Add or update a nickname to the irc_chan.users list.
 *
 * If the given nickname already exists in our users list, this does nothing. Otherwise, an irc_user is aquired using
 * irc_net_get_user, and a new irc_chan_user struct added to our users list.
 */
static err_t irc_chan_add_user (struct irc_chan *chan, const char *nickname)
{
    struct irc_user *user;
    struct irc_chan_user *chan_user;
    err_t err;

    // skip if already listed
    if (irc_chan_get_user(chan, nickname))
        return SUCCESS;
    
    // lookup/create the irc_user state, incrementing the refcount
    if ((err = irc_net_get_user(chan->net, &user, nickname)))
        return err;

    // alloc the new irc_chan_user
    if ((chan_user = calloc(1, sizeof(*chan_user))) == NULL) {
        // XXX: release irc_user
        return ERR_CALLOC;
    }

    // store
    chan_user->user = user;

    // add to users list
    LIST_INSERT_HEAD(&chan->users, chan_user, chan_users);

    // ok
    return SUCCESS;
}

/**
 * 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)
{
    struct irc_chan *chan = arg;
    err_t err;

    if (irc_conn_self(chan->net->conn, line->source->nickname)) {
        // twiddle state
        chan->joining = false;
        chan->joined = true;

        // invoke callback
        IRC_CHAN_INVOKE_CALLBACK(chan, on_self_join);

    } else {
        // add them
        if ((err = irc_chan_add_user(chan, line->source->nickname)))
            return log_warn("irc_chan_add_user(%s, %s): %s", irc_chan_name(chan), line->source->nickname, error_name(err));

        // invoke callback (source)
        IRC_CHAN_INVOKE_CALLBACK(chan, on_join, line->source);
    }
}

/**
 * :nm PRIVMSG <channel> <message>
 */
static void irc_chan_on_PRIVMSG (const struct irc_line *line, void *arg)
{
    struct irc_chan *chan = arg;

    const char *msg = line->args[1];

    // invoke callback (source, message)
    IRC_CHAN_INVOKE_CALLBACK(chan, on_msg, line->source, msg);
}

/**
 * Add/update nicknames to users list using irc_chan_add_user
 *
 * @see IRC_RPL_NAMREPLY
 */
static void irc_chan_on_RPL_NAMREPLY (const struct irc_line *line, void *arg)
{
    struct irc_chan *chan = arg;
    char chanflags[IRC_CHANFLAGS_MAX];
    const char *nickname;
    err_t err;

    // the args
    const char *arg_names = line->args[3];

    // copy the nicklist to a mutable buffer
    char names_buf[strlen(arg_names) + 1], *names = names_buf;
    strcpy(names, arg_names);

    // iterate over each name
    while ((nickname = strsep(&names, " "))) {
        // skip empty token at end
        if (strlen(nickname) == 0)
            continue;

        // parse off the channel flags
        nickname = irc_nick_chanflags(nickname, chanflags);

        // add/update
        // XXX: do something with chanflags
        if ((err = irc_chan_add_user(chan, nickname)))
            log_warn("irc_chan_add_user(%s, %s): %s", irc_chan_name(chan), nickname, error_name(err));
    }
}

/**
 * Channel join sequence complete
 *
 * @see IRC_RPL_ENDOFNAMES
 */
static void irc_chan_on_RPL_ENDOFNAMES (const struct irc_line *line, void *arg)
{
    struct irc_chan *chan = arg;

    (void) line;
    (void) chan;

    // XXX: update state
    log_info("channel join sync complete");
}

/**
 * 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;

    const char *msg = line->args[1];

    if (irc_conn_self(chan->net->conn, line->source->nickname)) {
        // twiddle state
        chan->joined = false;
        chan->parted = true;

        // invoke callback
        IRC_CHAN_INVOKE_CALLBACK(chan, on_self_part);
        
        // XXX: cleanup

    } else {
        // someone else
        struct irc_chan_user *chan_user;
        
        // invoke callback (source, msg)
        IRC_CHAN_INVOKE_CALLBACK(chan, on_part, line->source, msg);

        // look up the irc_chan_user
        if ((chan_user = irc_chan_get_user(chan, line->source->nickname)) == NULL)
            return log_warn("PART'd user not on channel: %s, %s", irc_chan_name(chan), line->source->nickname);

        // remove them
        irc_chan_remove_user(chan, chan_user);
    }
}

/**
 * :nm KICK <channel> <target> [<reason>]
 *
 * User kicked some other user, might be us
 */
static void irc_chan_on_KICK (const struct irc_line *line, void *arg)
{
    struct irc_chan *chan = arg;
    struct irc_chan_user *target_user;

    const char *target = line->args[1];
    const char *msg = line->args[2];

    if (irc_conn_self(chan->net->conn, target)) {
        // twiddle state
        chan->joined = false;
        chan->kicked = true;

        // invoke callback (source, msg)
        IRC_CHAN_INVOKE_CALLBACK(chan, on_self_kicked, line->source, msg);

        // XXX: cleanup

    } else {
        // look up the target irc_chan_user
        if ((target_user = irc_chan_get_user(chan, target)) == NULL)
            return log_warn("KICK'd user not on channel: %s, %s", irc_chan_name(chan), target);
        
        // invoke callback (source, target_user, msg)
        IRC_CHAN_INVOKE_CALLBACK(chan, on_kick, line->source, target_user, msg);

        // remove them
        irc_chan_remove_user(chan, target_user);
    }
}

/**
 * :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           },
    {   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           },
    {   "KICK",             &irc_chan_on_KICK           },
    {   "QUIT",             &irc_chan_on_QUIT           },
    {   NULL,       NULL                                }
};

err_t irc_chan_create (struct irc_chan **chan_ptr, struct irc_net *net, const struct irc_chan_info *info, error_t *err)
{
    struct irc_chan *chan;

    // allocate
    if ((chan = calloc(1, sizeof(*chan))) == NULL)
        return SET_ERROR(err, ERR_CALLOC);

    // store
    chan->net = net;
    
    // copy info
    if ((chan->info.channel = strdup(info->channel)) == NULL)
        JUMP_SET_ERROR(err, ERR_STRDUP);

    // init
    LIST_INIT(&chan->users);
    irc_cmd_init(&chan->handlers);
    CHAIN_INIT(&chan->callbacks);
    
    // add handlers
    if ((ERROR_CODE(err) = irc_cmd_add(&chan->handlers, _cmd_handlers, chan)))
        goto error;

    // ok
    *chan_ptr = chan;

    return SUCCESS;

error:
    // cleanup
    irc_chan_destroy(chan);

    return ERROR_CODE(err);
}

void irc_chan_destroy (struct irc_chan *chan)
{
    struct irc_chan_user *chan_user;

    // free users
    while ((chan_user = LIST_FIRST(&chan->users))) {
        irc_chan_remove_user(chan, chan_user);
    }

    // free chan itself
    irc_cmd_clear(&chan->handlers);
    CHAIN_CLEAR(&chan->callbacks);
    free((char *) chan->info.channel);
    free(chan);
}

err_t irc_chan_add_callbacks (struct irc_chan *chan, const struct irc_chan_callbacks *callbacks, void *arg)
{
    struct irc_chan_callback_item *item;
  
    // create a new item
    if ((item = CHAIN_ADD_TAIL(&chan->callbacks)) == NULL)
        return ERR_MEM;

    // store
    item->callbacks = callbacks;
    item->arg = arg;

    // ok
    return SUCCESS;
}

void irc_chan_remove_callbacks (struct irc_chan *chan, const struct irc_chan_callbacks *callbacks, void *arg)
{
    struct irc_chan_callback_item *item;
    
    // remove all matching callback_items
    CHAIN_DELETE_WHICH(&chan->callbacks, item, item->callbacks == callbacks && item->arg == arg);
}

struct irc_chan_user* irc_chan_get_user (struct irc_chan *chan, const char *nickname)
{
    struct irc_chan_user *chan_user = NULL;

    // look for it...
    LIST_FOREACH(chan_user, &chan->users, chan_users) {
        if (irc_cmp_nick(nickname, chan_user->user->nickname) == 0) {
            // found
            return chan_user;
        }
    }
    
    // not found
    return NULL;
}

err_t irc_chan_join (struct irc_chan *chan)
{
    err_t err;
    
    // correct state 
    if (chan->joining || chan->joined)
        return ERR_IRC_CHAN_STATE;

    if (!chan->net->conn)
        return ERR_IRC_NET_STATE;

    // send JOIN message on the appropriate connection
    if ((err = irc_conn_JOIN(chan->net->conn, chan->info.channel)))
        // XXX: error state?
        return err;

    // ok
    chan->joining = true;

    return SUCCESS;
}

err_t irc_chan_PRIVMSG (struct irc_chan *chan, const char *message)
{
    err_t err;

    // correct state 
    if (!chan->joined)
        return ERR_IRC_CHAN_STATE;

    if (!chan->net->conn)
        return ERR_IRC_NET_STATE;

    // send the PRIVMSG message
    if ((err = irc_conn_PRIVMSG(chan->net->conn, chan->info.channel, message)))
        return err;

    // invoke callback (msg)
    IRC_CHAN_INVOKE_CALLBACK(chan, on_self_msg, message);
    
    // ok
    return SUCCESS;
}

err_t irc_chan_NOTICE (struct irc_chan *chan, const char *message)
{
    err_t err;

    // correct state 
    if (!chan->joined)
        return ERR_IRC_CHAN_STATE;

    if (!chan->net->conn)
        return ERR_IRC_NET_STATE;

    // send the PRIVMSG message
    if ((err = irc_conn_NOTICE(chan->net->conn, chan->info.channel, message)))
        return err;

    // invoke callback (msg)
    IRC_CHAN_INVOKE_CALLBACK(chan, on_self_notice, message);
    
    // ok
    return SUCCESS;
}