--- a/src/irc_log.c Mon Mar 16 20:22:51 2009 +0200
+++ b/src/irc_log.c Mon Mar 16 21:47:18 2009 +0200
@@ -5,12 +5,13 @@
#include <stdlib.h>
#include <string.h>
+#include <assert.h> //<<< XXX: remove
#include <event2/event.h>
#include <evsql.h>
/**
- * The irc_log module state
+ * The irc_log module state.
*/
struct irc_log_ctx {
/** The nexus this module is loaded for */
@@ -18,9 +19,22 @@
/** The database connection */
struct evsql *db;
-
};
+/**
+ * State required to use irc_cmd_handler with irc_chan.handlers.
+ */
+struct irc_log_chan {
+ /** The irc_log context */
+ struct irc_log_ctx *ctx;
+
+ /** The target channel */
+ struct irc_chan *chan;
+};
+
+/**
+ * Our evsql result handler for irc_log_event INSERTs.
+ */
static void irc_log_on_sql_result (struct evsql_result *res, void *arg)
{
struct irc_log_ctx *ctx = arg;
@@ -35,10 +49,20 @@
evsql_result_free(res);
}
-static void irc_log_event (struct irc_log_ctx *ctx, struct irc_chan *chan, const struct irc_nm *source,
+/**
+ * Log the event into our database.
+ *
+ * Any of source, target, message can be NULL to insert NULLs
+ */
+static err_t irc_log_event (struct irc_log_ctx *ctx, struct irc_chan *chan, const struct irc_nm *source,
const char *type, const char *target, const char *message)
{
struct evsql_query *query;
+
+ // unpack the nick/user/hostname args, as source can be NULL
+ const char *nickname = source ? source->nickname : NULL;
+ const char *username = source ? source->username : NULL;
+ const char *hostname = source ? source->hostname : NULL;
// our SQL query
static struct evsql_query_info sql = {
@@ -58,39 +82,142 @@
EVSQL_TYPE_END
}
};
+
+ // drop lines if not connected
+ if (ctx->db == NULL) {
+ log_warn("no database connected");
+
+ return ERR_MODULE_CONF;
+ }
// run the INSERT
if ((query = evsql_query_exec(ctx->db, NULL, &sql, &irc_log_on_sql_result, ctx,
chan->net->info.network, irc_chan_name(chan),
- source->nickname, source->username, source->hostname,
+ nickname, username, hostname,
type, target, message
)) == NULL) {
// XXX: get evsql_query error info somehow...
log_error("evsql_query_exec failed for %s:%s - %s!%s@%s - %s -> %s - %s",
chan->net->info.network, irc_chan_name(chan),
- source->nickname, source->username, source->hostname,
- type, target, message
- );
-
- } else {
- log_debug("%s:%s - %s!%s@%s - %s -> %s - %s",
- chan->net->info.network, irc_chan_name(chan),
- source->nickname, source->username, source->hostname,
+ nickname, username, hostname,
type, target, message
);
+ return ERR_EVSQL_QUERY_EXEC;
}
+
+ // ok
+ log_debug("%s:%s - %s!%s@%s - %s -> %s - %s",
+ chan->net->info.network, irc_chan_name(chan),
+ nickname, username, hostname,
+ type, target, message
+ );
+
+ return SUCCESS;
}
-static void on_chan_msg (struct irc_chan *chan, const struct irc_nm *source, const char *message, void *arg)
+/**
+ * Parse the prefix into a nickmask and pass on to irc_log_event
+ */
+static err_t irc_log_event_prefix (struct irc_log_ctx *ctx, struct irc_chan *chan, const char *prefix,
+ const char *type, const char *target, const char *message)
{
- struct irc_log_ctx *ctx = arg;
+ char prefix_buf[IRC_PREFIX_MAX];
+ struct irc_nm nm;
+ err_t err;
- irc_log_event(ctx, chan, source, "PRIVMSG", NULL, message);
+ // parse nickmask
+ if ((err = irc_nm_parse(&nm, prefix_buf, prefix)))
+ return err;
+
+ // log
+ return irc_log_event(ctx, chan, &nm, type, target, message);
}
+/**
+ * Log a simple channel event of the form:
+ *
+ * :nm <type> <channel> [<msg>]
+ */
+static void irc_log_on_chan_generic (const struct irc_line *line, void *arg)
+{
+ struct irc_log_chan *chan_ctx = arg;
+
+ const char *msg = line->args[1];
+
+ irc_log_event_prefix(chan_ctx->ctx, chan_ctx->chan, line->prefix, line->command, NULL, msg);
+}
+
+/**
+ * Log a MODE event on a channel
+ *
+ * :nm MODE <channel> [<modearg> [...]]
+ *
+ * This conacts all the modeargs together, and logs that as the message
+ */
+static void irc_log_on_chan_MODE (const struct irc_line *line, void *arg)
+{
+ struct irc_log_chan *chan_ctx = arg;
+ char message[512], *ptr = message;
+ const char *cmdarg;
+
+ // iterate over each arg
+ FOREACH_IRC_LINE_ARGS(line, cmdarg) {
+ // separate with spaces
+ if (cmdarg > line->args[0])
+ *ptr++ = ' ';
+
+ // append
+ ptr += snprintf(ptr, (message + 512 - ptr), "%s", cmdarg);
+
+ // buffer full?
+ if (ptr > message + 512)
+ break;
+ }
+
+ // log
+ irc_log_event_prefix(chan_ctx->ctx, chan_ctx->chan, line->prefix, line->command, NULL, message);
+}
+
+/**
+ * Log a KICK event on a channel
+ *
+ * :nm KICK <channel> <target> [<comment>]
+ */
+static void irc_log_on_chan_KICK (const struct irc_line *line, void *arg)
+{
+ struct irc_log_chan *chan_ctx = arg;
+
+ const char *target = line->args[1];
+ const char *msg = line->args[2];
+
+ irc_log_event_prefix(chan_ctx->ctx, chan_ctx->chan, line->prefix, line->command, target, msg);
+}
+
+/**
+ * Our low-level channel-specific message handlers for logged channels.
+ *
+ * Note that these get a `struct irc_log_chan*` as an argument.
+ */
+static struct irc_cmd_handler _chan_cmd_handlers[] = {
+ { "JOIN", &irc_log_on_chan_generic },
+ { "PART", &irc_log_on_chan_generic },
+ { "MODE", &irc_log_on_chan_MODE },
+ { "TOPIC", &irc_log_on_chan_generic },
+ { "KICK", &irc_log_on_chan_KICK },
+ { "PRIVMSG", &irc_log_on_chan_generic },
+ { "NOTICE", &irc_log_on_chan_generic },
+ { NULL, NULL }
+};
+
+/**
+ * Our high-level channel-specific callbacks for logged channels.
+ *
+ * Note that these get a `struct irc_log_chan*` as an argument.
+ */
static struct irc_chan_callbacks _chan_callbacks = {
- .on_msg = on_chan_msg,
+ .on_self_join = NULL,
+ .on_msg = NULL,
};
static err_t irc_log_init (struct nexus *nexus, void **ctx_ptr, struct error_info *err)
@@ -115,33 +242,108 @@
return SET_ERROR(err, SUCCESS);
}
+/**
+ * Process the irc_log.db_info config option.
+ *
+ * Creates a new evsql handle.
+ *
+ * Fails if ctx->db is already set.
+ */
+static err_t irc_log_conf_db_info (struct irc_log_ctx *ctx, char *value, struct error_info *err)
+{
+ log_info("connect to database: %s", value);
+
+ // already connected?
+ if (ctx->db)
+ RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "irc_log.db_info already set");
+
+ // create a new evsql handle
+ if ((ctx->db = evsql_new_pq(ctx->nexus->ev_base, value, NULL, NULL)) == NULL)
+ return SET_ERROR(err, ERR_EVSQL_NEW_PQ);
+
+ // ok
+ return SUCCESS;
+}
+
+/**
+ * Process the irc_log.channel config option.
+ *
+ * Creates a new irc_log_chan context, looks up the channel, adds our command handlers, and logs an OPEN event.
+ *
+ * Fails if the value is invalid, we don't have a database connected, the channel doesn't exist, adding our command
+ * handlers/callbacks fails, or sending the initial INSERT-OPEN query fails.
+ */
+static err_t irc_log_conf_channel (struct irc_log_ctx *ctx, char *value, struct error_info *err)
+{
+ const char *network, *channel;
+
+ struct irc_log_chan *chan_ctx;
+
+ // parse required args
+ if ((network = strsep(&value, ":")) == NULL)
+ RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid <network> for irc_log.channel");
+
+ if ((channel = strsep(&value, ":")) == NULL)
+ RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid <channel> for irc_log.channel");
+
+ // extraneous stuff?
+ if (value)
+ RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "trailing data for irc_log.channel");
+
+ // have a db configured?
+ if (!ctx->db)
+ RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "irc_log.channel used without any irc_log.db_info");
+
+ // alloc
+ if ((chan_ctx = calloc(1, sizeof(*chan_ctx))) == NULL)
+ return SET_ERROR(err, ERR_CALLOC);
+
+ // store
+ chan_ctx->ctx = ctx;
+
+ // get the channel?
+ if ((chan_ctx->chan = irc_client_get_chan(ctx->nexus->client, network, channel)) == NULL)
+ JUMP_SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown channel name");
+
+ // add low-level handlers
+ if ((ERROR_CODE(err) = irc_cmd_add(&chan_ctx->chan->handlers, _chan_cmd_handlers, chan_ctx)))
+ goto error;
+
+ // add channel callbacks
+ if ((ERROR_CODE(err) = irc_chan_add_callbacks(chan_ctx->chan, &_chan_callbacks, chan_ctx)))
+ goto error;
+
+ // log an OPEN message
+ // XXX: move this to when we first JOIN the channel
+ if ((ERROR_CODE(err) = irc_log_event(ctx, chan_ctx->chan, NULL, "OPEN", NULL, NULL)))
+ goto error;
+
+ // ok
+ log_info("logging channel %s:%s", chan_ctx->chan->net->info.network, irc_chan_name(chan_ctx->chan));
+
+ return SUCCESS;
+
+error:
+ // XXX: remove callbacks
+ assert(!chan_ctx->chan);
+
+ free(chan_ctx);
+
+ return ERROR_CODE(err);
+}
+
+/**
+ * Set a config option
+ */
static err_t irc_log_conf (void *mod_ctx, const char *name, char *value, struct error_info *err)
{
struct irc_log_ctx *ctx = mod_ctx;
if (strcmp(name, "db_info") == 0) {
- log_info("connect to database: %s", value);
-
- if ((ctx->db = evsql_new_pq(ctx->nexus->ev_base, value, NULL, NULL)) == NULL)
- return ERR_EVSQL_NEW_PQ;
+ return irc_log_conf_db_info(ctx, value, err);
} else if (strcmp(name, "channel") == 0) {
- const char *network = strsep(&value, ":");
- const char *channel = value;
-
- struct irc_chan *chan;
-
- // kill missing tokens
- if (!network || !channel)
- RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid '<network>:<channel>' value");
-
- // get the channel?
- if ((chan = irc_client_get_chan(ctx->nexus->client, network, channel)) == NULL)
- RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown channel name");
-
- // add channel callbacks
- if ((ERROR_CODE(err) = irc_chan_add_callbacks(chan, &_chan_callbacks, ctx)))
- return ERROR_CODE(err);
+ return irc_log_conf_channel(ctx, value, err);
} else {
RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown configuration option");
@@ -158,3 +360,4 @@
.init = &irc_log_init,
.conf = &irc_log_conf,
};
+