add FindEvsql/FindLibPQ cmake modules and irc_log.sql definition, and implement logging of JOIN, PART, MODE, TOPIC, KICK, PRIVMSG, NOTICE and OPEN messages
authorTero Marttila <terom@fixme.fi>
Mon, 16 Mar 2009 21:47:18 +0200
changeset 68 591a574f390e
parent 67 aa94bf2b5f9b
child 69 6f298b6e0d5f
add FindEvsql/FindLibPQ cmake modules and irc_log.sql definition, and implement logging of JOIN, PART, MODE, TOPIC, KICK, PRIVMSG, NOTICE and OPEN messages
cmake/Modules/FindEvsql.cmake
cmake/Modules/FindLibPQ.cmake
src/CMakeLists.txt
src/error.c
src/error.h
src/irc_line.h
src/irc_log.c
src/irc_log.sql
src/irc_net.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/Modules/FindEvsql.cmake	Mon Mar 16 21:47:18 2009 +0200
@@ -0,0 +1,24 @@
+# Find evsql
+# Once done, this will define:
+#
+#   Evsql_FOUND
+#   Evsql_INCLUDE_DIRS
+#   Evsql_LIBRARIES
+#
+
+include (LibFindMacros)
+
+# include dir
+find_path (Evsql_INCLUDE_DIR
+    NAMES "evsql.h"
+)
+
+# library
+find_library (Evsql_LIBRARY
+    NAMES "evsql"
+)
+
+# set the external vars
+set (Evsql_PROCESS_INCLUDES Evsql_INCLUDE_DIR)
+set (Evsql_PROCESS_LIBS Evsql_LIBRARY)
+libfind_process (Evsql)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmake/Modules/FindLibPQ.cmake	Mon Mar 16 21:47:18 2009 +0200
@@ -0,0 +1,27 @@
+# Find libevent
+# Once done, this will define:
+#
+#   LibPQ_FOUND
+#   LibPQ_INCLUDE_DIRS
+#   LibPQ_LIBRARIES
+#
+# Currently, this only supports libevent-svn (i.e. 1.5/2.0), so it's kind of useless for real use :)
+
+include (LibFindMacros)
+
+# include dir
+find_path (LibPQ_INCLUDE_DIR
+    NAMES "postgresql/libpq-fe.h"
+    PATHS "$ENV{POSTGRESQL_PREFIX}/include"
+)
+
+# library
+find_library (LibPQ_LIBRARY
+    NAMES "pq"
+    PATHS "$ENV{POSTGRESQL_PREFIX}/lib"
+)
+
+# set the external vars
+set (LibPQ_PROCESS_INCLUDES LibPQ_INCLUDE_DIR)
+set (LibPQ_PROCESS_LIBS LibPQ_LIBRARY)
+libfind_process (LibPQ)
--- a/src/CMakeLists.txt	Mon Mar 16 20:22:51 2009 +0200
+++ b/src/CMakeLists.txt	Mon Mar 16 21:47:18 2009 +0200
@@ -21,7 +21,7 @@
 set (NEXUS_LIBRARIES ${LibEvent_LIBRARIES} ${GnuTLS_LIBRARIES} ${MODULE_LIBRARIES})
 
 # compiler flags
-set (CFLAGS "-Wall -Wextra")
+set (CFLAGS "-Wall -Wextra -std=gnu99")
 
 # add our binaries
 add_executable (nexus ${NEXUS_SOURCES})
--- a/src/error.c	Mon Mar 16 20:22:51 2009 +0200
+++ b/src/error.c	Mon Mar 16 21:47:18 2009 +0200
@@ -17,6 +17,7 @@
     {   ERR_EVENT_NEW,                      "event_new",                                ERR_EXTRA_NONE      },
     {   ERR_EVENT_ADD,                      "event_add",                                ERR_EXTRA_NONE      },
     {   ERR_EVSQL_NEW_PQ,                   "evsql_new_pq",                             ERR_EXTRA_NONE      },
+    {   ERR_EVSQL_QUERY_EXEC,               "evsql_query_exec",                         ERR_EXTRA_NONE      },
     {   ERR_CMD_OPT,                        "argv",                                     ERR_EXTRA_STR       },
     {   _ERR_INVALID,                       NULL,                                       0                   }
 
--- a/src/error.h	Mon Mar 16 20:22:51 2009 +0200
+++ b/src/error.h	Mon Mar 16 21:47:18 2009 +0200
@@ -64,6 +64,7 @@
     /** Evsql errors */
     _ERR_EVSQL      = 0x000600,
     ERR_EVSQL_NEW_PQ,
+    ERR_EVSQL_QUERY_EXEC,
 
     /** irc_proto errors */
     _ERR_IRC_LINE   = 0x000700,
--- a/src/irc_line.h	Mon Mar 16 20:22:51 2009 +0200
+++ b/src/irc_line.h	Mon Mar 16 21:47:18 2009 +0200
@@ -56,4 +56,10 @@
  */
 err_t irc_line_build (const struct irc_line *line, char *buf);
 
+/**
+ * Iterate through the arguments in an irc_line
+ */
+#define FOREACH_IRC_LINE_ARGS(line, arg_ptr) \
+    for (size_t _irc_line_arg_idx = 0; _irc_line_arg_idx < IRC_ARG_MAX && (arg_ptr = line->args[_irc_line_arg_idx]); _irc_line_arg_idx++)
+
 #endif /* IRC_LINE_H */
--- 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,
 };
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/irc_log.sql	Mon Mar 16 21:47:18 2009 +0200
@@ -0,0 +1,14 @@
+
+CREATE TABLE logs (
+    id          serial,
+    network     varchar(32)     NOT NULL,
+    channel     varchar(32)     NOT NULL,
+    ts          timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,       -- UTC time
+    nickname    varchar(32),                    -- source nickname, NULL for "meta" events
+    username    varchar(32),                    -- source username, NULL for "meta" events
+    hostname    varchar(64),                    -- source hostname, NULL for "meta" events
+    type        varchar(16)     NOT NULL,       -- event type
+    target      varchar(32),                    -- optional target
+    message     varchar(512)                    -- optional event data
+);
+
--- a/src/irc_net.c	Mon Mar 16 20:22:51 2009 +0200
+++ b/src/irc_net.c	Mon Mar 16 21:47:18 2009 +0200
@@ -108,11 +108,11 @@
 }
 
 /**
- * :nm PRIVMSG <target> <message>
+ * :nm (PRIVMSG|NOTICE) <target> <message>
  *
  * Either propagate to channel if found, otherwise handle as a privmsg
  */
-static void irc_net_on_PRIVMSG (const struct irc_line *line, void *arg)
+static void irc_net_on_msg (const struct irc_line *line, void *arg)
 {
     struct irc_net *net = arg;
 
@@ -132,7 +132,16 @@
  */
 static struct irc_cmd_handler _cmd_handlers[] = {
     {   "JOIN",     &irc_net_on_chan0       },
-    {   "PRIVMSG",  &irc_net_on_PRIVMSG     },
+    {   "PART",     &irc_net_on_chan0       },
+    {   "MODE",     &irc_net_on_chan0       },
+    {   "TOPIC",    &irc_net_on_chan0       },
+    {   "KICK",     &irc_net_on_chan0       },
+
+    {   "PRIVMSG",  &irc_net_on_msg         },
+    {   "NOTICE",   &irc_net_on_msg         },
+
+    // XXX: NICK/QUIT
+
     {   NULL,       NULL                    }
 };