add a simple irc_log module (with evsql code) that joins a channel and log_info's PRIVMSGs
authorTero Marttila <terom@fixme.fi>
Sun, 08 Mar 2009 17:17:37 +0200
changeset 23 542c73d07d3c
parent 22 c339c020fd33
child 24 08a26d0b9afd
add a simple irc_log module (with evsql code) that joins a channel and log_info's PRIVMSGs
Makefile
src/error.h
src/irc_cmd.h
src/irc_conn.c
src/irc_conn.h
src/irc_line.c
src/irc_log.c
src/irc_log.h
src/nexus.c
--- a/Makefile	Sun Mar 01 02:02:48 2009 +0200
+++ b/Makefile	Sun Mar 08 17:17:37 2009 +0200
@@ -24,6 +24,10 @@
 GNUTLS_CFLAGS = $(shell pkg-config ${GNUTLS_NAME} --cflags)
 GNUTLS_LDFLAGS = $(shell pkg-config ${GNUTLS_NAME} --libs)
 
+# evsql stuff
+EVSQL_CFLAGS = 
+EVSQL_LDFLAGS = -levsql -lpq
+
 BIN_NAMES = nexus
 BIN_PATHS = $(addprefix bin/,$(BIN_NAMES))
 
@@ -35,6 +39,7 @@
 SOCK_GNUTLS_OBJS = obj/sock_gnutls.o
 LINEPROTO_OBJS = obj/line_proto.o
 IRC_OBJS = obj/irc_line.o obj/irc_conn.o
+IRC_LOG_OBJS = obj/irc_log.o
 
 # XXX: not yet there
 #CORE_OBJS = obj/lib/log.o obj/lib/signals.o
@@ -43,11 +48,11 @@
 all: ${BIN_PATHS}
 
 # binaries
-bin/nexus: ${CORE_OBJS} ${SOCK_OBJS} ${SOCK_GNUTLS_OBJS} ${LINEPROTO_OBJS} ${IRC_OBJS}
+bin/nexus: ${CORE_OBJS} ${SOCK_OBJS} ${SOCK_GNUTLS_OBJS} ${LINEPROTO_OBJS} ${IRC_OBJS} ${IRC_LOG_OBJS}
 
 # computed
-CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS} ${LIBEVENT_CFLAGS} ${GNUTLS_CFLAGS}
-LDFLAGS = ${LIBEVENT_LDFLAGS} ${GNUTLS_LDFLAGS}
+CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS} ${LIBEVENT_CFLAGS} ${GNUTLS_CFLAGS} ${EVSQL_CFLAGS}
+LDFLAGS = ${LIBEVENT_LDFLAGS} ${GNUTLS_LDFLAGS} ${EVSQL_LDFLAGS}
 
 # XXX: is this valid?
 CPPFLAGS = ${CFLAGS}
--- a/src/error.h	Sun Mar 01 02:02:48 2009 +0200
+++ b/src/error.h	Sun Mar 08 17:17:37 2009 +0200
@@ -68,6 +68,9 @@
     _ERROR_CODE( ERR_EVENT_NEW,                     0x010201,   NONE    ),
     _ERROR_CODE( ERR_EVENT_ADD,                     0x010202,   NONE    ),
 
+    /* Evsql errors */
+    _ERROR_CODE( ERR_EVSQL_NEW_PQ,                  0x010301,   NONE    ),
+
     /* irc_line errors */
     _ERROR_CODE( ERR_LINE_TOO_LONG,                 0x100101,   NONE    ),
     _ERROR_CODE( ERR_LINE_INVALID_TOKEN,            0x100102,   NONE    ),
--- a/src/irc_cmd.h	Sun Mar 01 02:02:48 2009 +0200
+++ b/src/irc_cmd.h	Sun Mar 08 17:17:37 2009 +0200
@@ -1,6 +1,41 @@
 #ifndef IRC_CMD_H
 #define IRC_CMD_H
 
+/**
+ * @file
+ *
+ * Flexible command handlers callback lists for use with irc_lines
+ */
+
+#include "irc_conn.h"
+#include "irc_line.h"
+#include <sys/queue.h>
+
+/**
+ * Single command -> handler mapping for lookup
+ */
+struct irc_cmd_handler {
+    /** The command name to match */
+    const char *command;
+
+    /** The handler function */
+    void (*func) (struct irc_conn *conn, const struct irc_line *line, void *arg);
+};
+
+/**
+ * List item for a chain of irc_cmd_handler entries, with the context pointer
+ */
+struct irc_cmd_chain {
+    /** The list of handler lookup entries */
+    struct irc_cmd_handler *handlers;
+
+    /** Opaque context arg */
+    void *arg;
+
+    /** Linked list */
+    STAILQ_ENTRY(irc_cmd_chain) node;
+};
+
 /*
  * IRC command numerics
  */
--- a/src/irc_conn.c	Sun Mar 01 02:02:48 2009 +0200
+++ b/src/irc_conn.c	Sun Mar 08 17:17:37 2009 +0200
@@ -9,7 +9,7 @@
 /*
  * "Welcome to the Internet Relay Network <nick>!<user>@<host>"
  */
-static void on_RPL_WELCOME (struct irc_conn *conn, const struct irc_line *line)
+static void on_RPL_WELCOME (struct irc_conn *conn, const struct irc_line *line, void *arg)
 {
     // update state
     conn->registered = true;
@@ -22,7 +22,7 @@
  *
  * Send a 'PONG <server1>` reply right away.
  */ 
-static void on_PING (struct irc_conn *conn, const struct irc_line *line)
+static void on_PING (struct irc_conn *conn, const struct irc_line *line, void *arg)
 {
     // just reply
     irc_conn_PONG(conn, line->args[0]);
@@ -31,23 +31,20 @@
 /*
  * Our command handlers
  */
-struct irc_cmd_handler {
-    /* The command name */
-    const char *command;
-
-    /* The handler function */
-    void (*func) (struct irc_conn *conn, const struct irc_line *line);
-
-} _cmd_handlers[] = {
+struct irc_cmd_handler _cmd_handlers[] = {
     { IRC_RPL_WELCOME,  on_RPL_WELCOME      },
     { "PING",           on_PING             },
     { NULL,             NULL,               },
 };
 
+/*
+ * Incoming line handler
+ */
 void irc_conn_on_line (char *line_buf, void *arg) 
 {
     struct irc_conn *conn = arg;
     struct irc_line line;
+    struct irc_cmd_chain *chain;
     struct irc_cmd_handler *handler;
     int err;
     
@@ -56,18 +53,22 @@
 
     // parse
     if ((err = irc_line_parse(&line, line_buf))) {
-        printf("!!! Invalid line: %s: %s\n", line_buf, error_name(err));
-        
+        log_warn("invalid line: %s: %s\n", line_buf, error_name(err));
         return;
     }
+    
+    // run each handler chain
+    STAILQ_FOREACH(chain, &conn->handlers, node) {
+        // look up appropriate handler
+        for (handler = chain->handlers; handler->command; handler++) {
+            // the command is alpha-only, so normal case-insensitive cmp is fine
+            if (strcasecmp(handler->command, line.command) == 0) {
+                // invoke the func
+                handler->func(conn, &line, chain->arg);
 
-    // look up appropriate handler
-    for (handler = _cmd_handlers; handler->command; handler++) {
-        // the command is alpha-only, so normal case-insensitive cmp is fine
-        if (strcasecmp(handler->command, line.command) == 0) {
-            // invoke the func
-            handler->func(conn, &line);
-            break;
+                // ...only one per chain
+                break;
+            }
         }
     }
 }
@@ -80,6 +81,13 @@
     if ((conn = calloc(1, sizeof(struct irc_conn))) == NULL)
         return SET_ERROR(err, ERR_CALLOC);
 
+    // initialize command handlers
+    STAILQ_INIT(&conn->handlers);
+    
+    // add the core handlers 
+    if ((ERROR_CODE(err) = irc_conn_register_handler_chain(conn, _cmd_handlers, NULL)))
+        return ERROR_CODE(err);
+
     // create the line_proto, with our on_line handler
     if (line_proto_create(&conn->lp, sock, IRC_LINE_MAX * 1.5, &irc_conn_on_line, conn, err))
         return ERROR_CODE(err);
@@ -97,6 +105,25 @@
     return SUCCESS;
 }
 
+err_t irc_conn_register_handler_chain (struct irc_conn *conn, struct irc_cmd_handler *handlers, void *arg)
+{
+    struct irc_cmd_chain *item;
+
+    // allocate the chain item
+    if ((item = calloc(1, sizeof(*item))) == NULL)
+        return ERR_CALLOC;
+
+    // store
+    item->handlers = handlers;
+    item->arg = arg;
+
+    // append
+    STAILQ_INSERT_TAIL(&conn->handlers, item, node);
+
+    // ok
+    return SUCCESS;
+}
+
 err_t irc_conn_send (struct irc_conn *conn, const struct irc_line *line)
 {
     char line_buf[IRC_LINE_MAX + 2];
@@ -148,3 +175,13 @@
     return irc_conn_send(conn, &line);
 }
 
+err_t irc_conn_JOIN (struct irc_conn *conn, const char *channel)
+{
+    // JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) / "0"
+    struct irc_line line = {
+        NULL, "JOIN", { channel, NULL }
+    };
+
+    return irc_conn_send(conn, &line);
+}
+
--- a/src/irc_conn.h	Sun Mar 01 02:02:48 2009 +0200
+++ b/src/irc_conn.h	Sun Mar 08 17:17:37 2009 +0200
@@ -1,18 +1,25 @@
 #ifndef IRC_CONN_H
 #define IRC_CONN_H
 
-/*
- * Per-connection IRC setup and state/protocol handling.
+/**
+ * @file
+ *
+ * Support for connections to IRC servers, a rather fundamental thing. This holds the line_proto to handle the network
+ * communications, and then takes care of sending/receiving commands, as well as updating some core state like
+ * current nickname.
  */
 
+struct irc_conn;
+
+#include "error.h"
 #include "sock.h"
 #include "line_proto.h"
 #include "irc_line.h"
-#include "error.h"
+#include "irc_cmd.h"
 #include <stdbool.h>
 
 /*
- * A connection to an IRC server.
+ * Connection state
  */
 struct irc_conn {
     /* We are a line-based protocol */
@@ -20,6 +27,9 @@
 
     /* Registered (as in, we have a working nickname)? */
     bool registered;
+
+    /* Command handlers */
+    STAILQ_HEAD(irc_conn_handlers, irc_cmd_chain) handlers;
 };
 
 // XXX: this should probably be slightly reworked
@@ -34,16 +44,31 @@
     const char *realname;
 };
 
-/*
+/**
  * Create a new irc_conn using the given sock_stream, which should be connected to an IRC server. The parameters given
  * in \a config will be used to identify with the IRC server.
  *
  * On success, the resulting irc_conn is returned via *conn with SUCCESS. Otherwise, -ERR_* and error info is returned
  * via *err.
+ *
+ * @param conn the new irc_conn structure is returned via this pointer
+ * @param sock the socket connected to the IRC server
+ * @param config the basic information used to register
+ * @param err errors are returned via this pointer
+ * @return error code
  */
 err_t irc_conn_create (struct irc_conn **conn, struct sock_stream *sock, const struct irc_conn_config *config, struct error_info *err);
 
 /**
+ * Add a new chain of command handler callbacks to be used to handle irc_lines from the server. The given arg will be
+ * passed to the callbacks as the context argument. The chain will be appended to the end of the current list of chains.
+ *
+ * @param chain the array of irc_cmd_handler structs, terminated with a NULL entry
+ * @param arg the context argument to use for the callbacks
+ */
+err_t irc_conn_register_handler_chain (struct irc_conn *conn, struct irc_cmd_handler *handlers, void *arg);
+
+/**
  * @group Simple request functions
  *
  * The error handling of these functions is such that the error return code is can be used or ignored as convenient,
@@ -52,26 +77,46 @@
  * @{
  */
 
-/*
+/**
  * Send a generic IRC message
+ *
+ * @param conn the IRC protocol connection
+ * @param line the irc_line protocol line to send
+ * @return error code
  */
 err_t irc_conn_send (struct irc_conn *conn, const struct irc_line *line);
 
-/*
+/**
  * Send a NICK message
+ *
+ * @param nickname the new nickname to use
  */
 err_t irc_conn_NICK (struct irc_conn *conn, const char *nickname);
 
 /*
  * Send a USER message
+ *
+ * @param username the username to register with, may be replaced with ident reply
+ * @param realname the full-name to register with
  */
 err_t irc_conn_USER (struct irc_conn *conn, const char *username, const char *realname);
 
 /*
  * Send a PONG message to the given target
+ *
+ * @param target the PING source, aka. the target to send the PONG reply to
  */
 err_t irc_conn_PONG (struct irc_conn *conn, const char *target);
 
+/*
+ * Send a JOIN message for the given channel
+ *
+ * XXX: this doesn't implement the full command options
+ *
+ * @param channel the full channel name to join
+ */
+err_t irc_conn_JOIN (struct irc_conn *conn, const char *channel);
+
 // @}
 
 #endif /* IRC_CONN_H */
--- a/src/irc_line.c	Sun Mar 01 02:02:48 2009 +0200
+++ b/src/irc_line.c	Sun Mar 08 17:17:37 2009 +0200
@@ -55,7 +55,7 @@
     len = token_len + (flags & TOK_NOSPACE ? 0 : 1) + (flags & TOK_TRAILING ? 1 : 0);
     
     // check overflow
-    if (*offset + len > IRC_LINE_MAX)
+    if (*offset + len + 1 > IRC_LINE_MAX)
         return ERR_LINE_TOO_LONG;
 
     // check invalid tokens
@@ -112,6 +112,9 @@
        }
     }
 
+    // terminate, output_token reserved the space for this
+    buf[off] = '\0';
+
     // done
     return SUCCESS;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/irc_log.c	Sun Mar 08 17:17:37 2009 +0200
@@ -0,0 +1,58 @@
+#include "irc_log.h"
+#include "log.h"
+
+// XXX: fix this err_t crap
+#define LIB_ERR_H
+#include <evsql.h>
+
+/**
+ * The core irc_log state
+ */
+static struct irc_log_ctx {
+    /** The database connection */
+    struct evsql *db;
+
+} _ctx;
+
+static void on_PRIVMSG (struct irc_conn *conn, const struct irc_line *line, void *arg)
+{
+    struct irc_log_ctx *ctx = arg;
+
+    // log it! :P
+    log_debug("%s: %s: %s", line->prefix, line->args[0], line->args[1]);
+}
+
+static struct irc_cmd_handler _cmd_handlers[] = {
+    {   "PRIVMSG",  &on_PRIVMSG     },
+    {   NULL,       NULL            }
+};
+
+err_t irc_log_init (struct event_base *ev_base, const char *db_info, struct irc_conn *irc, const char *channel)
+{
+    struct irc_log_ctx *ctx = &_ctx;
+    err_t err;
+
+    // open the database connection
+    if (db_info) {
+        log_info("connect to database: %s", db_info);
+
+        if ((ctx->db = evsql_new_pq(ev_base, db_info, NULL, NULL)) == NULL)
+           return ERR_EVSQL_NEW_PQ;
+    }
+    
+    if (channel) {
+        log_info("join channel: %s", channel);
+
+        // join the channel
+        if ((err = irc_conn_JOIN(irc, channel)))
+            return err;
+    }
+
+    // register for events
+    if ((err = irc_conn_register_handler_chain(irc, _cmd_handlers, ctx)))
+        return err;
+
+    // ok
+    return SUCCESS;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/irc_log.h	Sun Mar 08 17:17:37 2009 +0200
@@ -0,0 +1,21 @@
+#ifndef IRC_LOG_H
+#define IRC_LOG_H
+
+/**
+ * @file
+ *
+ * Logging IRC events to an SQL database
+ */
+
+#include "error.h"
+#include "irc_conn.h"
+#include <event2/event.h>
+
+/**
+ * Initialize the global irc_log state
+ *
+ * XXX: db_info is still unused if not specified
+ */
+err_t irc_log_init (struct event_base *ev_base, const char *db_info, struct irc_conn *irc, const char *channel);
+
+#endif
--- a/src/nexus.c	Sun Mar 01 02:02:48 2009 +0200
+++ b/src/nexus.c	Sun Mar 08 17:17:37 2009 +0200
@@ -2,6 +2,7 @@
 #include "log.h"
 #include "sock.h"
 #include "irc_conn.h"
+#include "irc_log.h"
 
 #include <stdlib.h>
 #include <stdbool.h>
@@ -15,12 +16,21 @@
 #define DEFAULT_PORT "6667"
 #define DEFAULT_PORT_SSL "6697"
 
+enum option_code {
+    _OPT_LOG_BEGIN      = 0x00ff,
+
+    OPT_LOG_DATABASE,
+    OPT_LOG_CHANNEL,
+};
+
 static struct option options[] = {
-    {"help",            0,  NULL,   'h' },
-    {"hostname",        1,  NULL,   'H' },
-    {"port",            1,  NULL,   'P' },
-    {"ssl",             0,  NULL,   'S' },
-    {0,                 0,  0,      0   },
+    {"help",            0,  NULL,   'h'                 },
+    {"hostname",        1,  NULL,   'H'                 },
+    {"port",            1,  NULL,   'P'                 },
+    {"ssl",             0,  NULL,   'S'                 },
+    {"log-database",    1,  NULL,   OPT_LOG_DATABASE    },
+    {"log-channel",     1,  NULL,   OPT_LOG_CHANNEL     },
+    {0,                 0,  0,      0                   },
 };
 
 void usage (const char *exe) 
@@ -31,6 +41,8 @@
     printf(" --hostname / -H HOST   set hostname to connect to\n");
     printf(" --port / -P PORT       set service port to connect to\n");
     printf(" --ssl / -S             use SSL\n");
+    printf(" --log-database         database connection string for logging\n");
+    printf(" --log-channel          channel to log\n");
 }
 
 int main (int argc, char **argv) 
@@ -48,6 +60,7 @@
         .username       = "spbot-dev",
         .realname       = "SpBot (development version)",
     };
+    const char *log_database = NULL, *log_channel = NULL;
 
     bool port_default = true;
     
@@ -74,6 +87,14 @@
                     portname = DEFAULT_PORT_SSL;
 
                 break;
+            
+            case OPT_LOG_DATABASE:
+                log_database = optarg;
+                break;
+
+            case OPT_LOG_CHANNEL:
+                log_channel = optarg;
+                break;
 
             case '?':
                 usage(argv[0]);
@@ -109,6 +130,12 @@
     // create the irc connection state
     if (irc_conn_create(&conn, sock, &conn_config, &err))
         FATAL_ERROR(&err, "irc_conn_create");
+    
+    // logging?
+    if (log_database || log_channel) {
+        if ((ERROR_CODE(&err) = irc_log_init(ev_base, log_database, conn, log_channel)))
+            FATAL_ERROR(&err, "irc_log_init");
+    }
 
     // run event loop
     if (event_base_dispatch(ev_base))