Fix #111.
[jelmer/ctrlproxy.git] / mods / log_custom.c
index 13f777f3b8a89be03d6bdc1293d64aef71235931..e1a22bfa21d2434957bd5fd50a2a539f00aa271b 100644 (file)
 #include <stdio.h>
 #include <time.h>
 #include <glib.h>
+#include <glib/gstdio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#ifdef _WIN32
-#include <direct.h>
-#define mkdir(s,t) _mkdir(s)
-#endif
-
-
 #define MAX_SUBST 256
 #undef G_LOG_DOMAIN
 #define G_LOG_DOMAIN "log_custom"
 
-const char *logfilename = NULL;
-GHashTable *fmts = NULL;
+struct file_info {
+       FILE *file;
+       time_t last_used;
+};
+
+struct log_custom_data {
+    char *logfilename;
+    GKeyFile *kf;
+};
+
+static GHashTable *files;
 
 /* Translation table */
 struct log_mapping {
@@ -47,51 +51,59 @@ struct log_mapping {
        char subst;
        unsigned int index;
        /* If index is -1 */
-       char *(*callback) (struct network *, struct line *l, gboolean case_sensitive);
+       char *(*callback) (struct network *, const struct line *l, gboolean case_sensitive);
 };
 
-static char *get_hours(struct network *n, struct line *l, gboolean case_sensitive) { 
+static char *get_hours(struct network *n, const struct line *l, gboolean case_sensitive) 
+{ 
        time_t ti = time(NULL);
        struct tm *t = localtime(&ti);
        return g_strdup_printf("%02d", t->tm_hour);
 }
 
-static char *get_minutes(struct network *n, struct line *l, gboolean case_sensitive) { 
+static char *get_minutes(struct network *n, const struct line *l, gboolean case_sensitive) 
+{ 
        time_t ti = time(NULL);
        struct tm *t = localtime(&ti);
        return g_strdup_printf("%02d", t->tm_min);
 }
 
-static char *get_seconds(struct network *n, struct line *l, gboolean case_sensitive) { 
+static char *get_seconds(struct network *n, const struct line *l, gboolean case_sensitive) 
+{ 
        time_t ti = time(NULL);
        struct tm *t = localtime(&ti);
        return g_strdup_printf("%02d", t->tm_sec);
 }
 
-static char *get_seconds_since_1970(struct network *n, struct line *l, gboolean case_sensitive) {
+static char *get_seconds_since_1970(struct network *n, const struct line *l, gboolean case_sensitive) 
+{
        time_t ti = time(NULL);
        return g_strdup_printf("%ld", ti);
 }
 
-static char *get_day(struct network *n, struct line *l, gboolean case_sensitive) { 
+static char *get_day(struct network *n, const struct line *l, gboolean case_sensitive) 
+{ 
        time_t ti = time(NULL);
        struct tm *t = localtime(&ti);
        return g_strdup_printf("%02d", t->tm_mday);
 }
 
-static char *get_month(struct network *n, struct line *l, gboolean case_sensitive) { 
+static char *get_month(struct network *n, const struct line *l, gboolean case_sensitive) 
+{ 
        time_t ti = time(NULL);
        struct tm *t = localtime(&ti);
        return g_strdup_printf("%02d", t->tm_mon + 1);
 }
 
-static char *get_year(struct network *n, struct line *l, gboolean case_sensitive) { 
+static char *get_year(struct network *n, const struct line *l, gboolean case_sensitive) 
+{ 
        time_t ti = time(NULL);
        struct tm *t = localtime(&ti);
        return g_strdup_printf("%04d", t->tm_year + 1900);
 }
 
-static char *get_user(struct network *n, struct line *l, gboolean case_sensitive) {
+static char *get_user(struct network *n, const struct line *l, gboolean case_sensitive) 
+{
        char *nick = NULL;
        char *user = NULL;
 
@@ -103,37 +115,49 @@ static char *get_user(struct network *n, struct line *l, gboolean case_sensitive
        else return g_strdup(user);
 }
 
-static char *get_monthname(struct network *n, struct line *l, gboolean case_sensitive) { 
+static char *get_monthname(struct network *n, const struct line *l, gboolean case_sensitive) 
+{ 
        char stime[512];
        time_t ti = time(NULL);
        strftime(stime, sizeof(stime), "%b", localtime(&ti));
        return g_strdup_printf("%s", stime);
 }
 
-static char *get_nick(struct network *n, struct line *l, gboolean case_sensitive) {
+static char *get_nick(struct network *n, const struct line *l, gboolean case_sensitive) 
+{
        if (l->origin) {
-               if(case_sensitive) return g_ascii_strdown(line_get_nick(l), -1);
-               else return g_strdup(line_get_nick(l)); 
+               char *n = line_get_nick(l);
+               if(case_sensitive) {
+                       char *r = g_ascii_strdown(n, -1);
+                       g_free(n);
+                       return r;
+               }
+               else return n;
        }
        
        return g_strdup("");
 }
 
-static char *get_network(struct network *n, struct line *l, gboolean case_sensitive) 
+static char *get_network(struct network *n, const struct line *l, gboolean case_sensitive) 
 { return g_strdup(n->name); }
-static char *get_server(struct network *n, struct line *l, gboolean case_sensitive)
-{ return g_strdup(n->connection.data.tcp.current_server->name); }
 
-static char *get_percent(struct network *n, struct line *l, gboolean case_sensitive) { return g_strdup("%"); }
+static char *get_server(struct network *n, const struct line *l, gboolean case_sensitive)
+{
+       if (n->connection.data.tcp.current_server)
+               return g_strdup(n->connection.data.tcp.current_server->host);
+       return g_strdup("");
+}
+
+static char *get_percent(struct network *n, const struct line *l, gboolean case_sensitive) { return g_strdup("%"); }
 
 static const char *identifier = NULL;
 
-static char *get_identifier(struct network *n, struct line *l, gboolean case_sensitive) { 
+static char *get_identifier(struct network *n, const struct line *l, gboolean case_sensitive) { 
        if(case_sensitive) return g_ascii_strdown(identifier, -1); 
        else return g_strdup(identifier); 
 }
 
-static char *get_modechanges(struct network *n, struct line *l, gboolean case_sensitive) {
+static char *get_modechanges(struct network *n, const struct line *l, gboolean case_sensitive) {
        char buf[512] = "";
        int i;
 
@@ -191,7 +215,7 @@ static struct log_mapping mappings[] = {
        { NULL }
 };
 
-static char *find_mapping(struct network *network, struct line *l, char c, gboolean case_sensitive)
+static char *find_mapping(struct network *network, const struct line *l, char c, gboolean case_sensitive)
 {
        int i;
        for(i = 0; mappings[i].subst; i++) {
@@ -213,7 +237,7 @@ static void convertslashes(char *a)
        for (j = 0; a[j]; j++) if (a[j] == '/') a[j] = '_';
 }
 
-static void custom_subst(struct network *network, char **_new, const char *fmt, struct line *l, const char *_identifier, gboolean case_sensitive, gboolean noslash)
+static void custom_subst(struct network *network, char **_new, const char *fmt, const struct line *l, const char *_identifier, gboolean case_sensitive, gboolean noslash)
 {
        char *subst[MAX_SUBST];
        char *new;
@@ -284,16 +308,14 @@ If appropriate:
  -- NICK: %r
  */
 
-static GHashTable *files = NULL;
-
-static FILE *find_add_channel_file(struct network *network, struct line *l, const char *identifier, gboolean create_file) 
+static FILE *find_add_channel_file(struct log_custom_data *data, struct network *network, const struct line *l, const char *identifier, gboolean create_file) 
 {
        char *n = NULL, *dn, *p;
-       FILE *f;
-       if(!logfilename) return NULL;
-       custom_subst(network, &n, logfilename, l, identifier, TRUE, TRUE);
-       f = g_hash_table_lookup(files, n);
-       if(!f && create_file) {
+       struct file_info *fi;
+       if(!data->logfilename) return NULL;
+       custom_subst(network, &n, data->logfilename, l, identifier, TRUE, TRUE);
+       fi = g_hash_table_lookup(files, n);
+       if(fi == NULL && create_file) {
                dn = g_strdup(n);
                
                /* Only include directory-part */
@@ -301,42 +323,56 @@ static FILE *find_add_channel_file(struct network *network, struct line *l, cons
                if(p) *p = '\0';
 
                /* Check if directory needs to be created */
-               if(!g_file_test(dn, G_FILE_TEST_IS_DIR) && mkdir(dn, 0700) == -1) {
-                       log_network("log_custom", LOG_ERROR, network, "Couldn't create directory %s for logging!", dn);
+               if(!g_file_test(dn, G_FILE_TEST_IS_DIR) && g_mkdir(dn, 0700) == -1) {
+                       log_network(LOG_ERROR, network, "Couldn't create directory %s for logging!", dn);
                        g_free(dn);
                        g_free(n);
                        return NULL;
                }
                g_free(dn);
+
+               fi = g_new0(struct file_info, 1);
                
                /* Then open the correct filename */
-               f = fopen(n, "a+");
-               if(!f) {
-                       log_network("log_custom", LOG_ERROR, network, "Couldn't open file %s for logging!", n);
+               fi->file = fopen(n, "a+");
+               if(!fi->file) {
+                       log_network(LOG_ERROR, network, "Couldn't open file %s for logging!", n);
                        g_free(n);
+                       g_free(fi);
                        return NULL;
                }
-               g_hash_table_insert(files, n, f);
+               g_hash_table_insert(files, n, fi);
        } else g_free(n);
-       return f;
+
+       if (fi == NULL)
+               return NULL;
+
+       fi->last_used = time(NULL);
+       return fi->file;
 }
 
-static void file_write_target(struct network *network, const char *n, struct line *l) 
+static void file_write_target(struct log_custom_data *data, struct network *network, const char *n, const struct line *l) 
 {
        char *t, *s, *fmt;
        FILE *f;
-
-       fmt = g_hash_table_lookup(fmts, n);
+       
+       fmt = g_key_file_get_string(data->kf, "log-custom", n, NULL);
        if(!fmt) return;
-
-       if(!irccmp(network->state->info, network->state->me.nick, l->args[1])) {
-               if (l->origin) t = g_strdup(line_get_nick(l));
+       
+       g_assert(l->args[0]);
+       g_assert(l->args[1]);
+       g_assert(network->state);
+       g_assert(network->state->me.nick);
+       g_assert(network->state->info);
+
+       if (!irccmp(network->state->info, network->state->me.nick, l->args[1])) {
+               if (l->origin) t = line_get_nick(l);
                else t = g_strdup("_messages_");
        } else {
                t = g_strdup(l->args[1]);
        }
 
-       f = find_add_channel_file(network, l, t, TRUE);
+       f = find_add_channel_file(data, network, l, t, TRUE);
        if(!f) { g_free(t); return; }
        
        custom_subst(network, &s, fmt, l, t, FALSE, FALSE);
@@ -349,15 +385,15 @@ static void file_write_target(struct network *network, const char *n, struct lin
        g_free(s);
 }
 
-static void file_write_channel_only(struct network *network, const char *n, struct line *l)
+static void file_write_channel_only(struct log_custom_data *data, struct network *network, const char *n, const struct line *l)
 {
        char *s, *fmt;
        FILE *f;
 
-       fmt = g_hash_table_lookup(fmts, n);
+       fmt = g_key_file_get_string(data->kf, "log-custom", n, NULL);
        if(!fmt) return;
 
-       f = find_add_channel_file(network, l, l->args[1], TRUE);
+       f = find_add_channel_file(data, network, l, l->args[1], TRUE);
        if(!f) return; 
 
        custom_subst(network, &s, fmt, l, l->args[1], FALSE, FALSE);
@@ -368,7 +404,7 @@ static void file_write_channel_only(struct network *network, const char *n, stru
        g_free(s);
 }
 
-static void file_write_channel_query(struct network *network, const char *n, struct line *l)
+static void file_write_channel_query(struct log_custom_data *data, struct network *network, const char *n, const struct line *l)
 {
        char *s, *fmt;
        char *nick;
@@ -377,13 +413,15 @@ static void file_write_channel_query(struct network *network, const char *n, str
        struct network_nick *nn;
 
        if (!l->origin) return;
-       nick = line_get_nick(l);
 
-       fmt = g_hash_table_lookup(fmts, n);
+       g_assert(n);
+
+       fmt = g_key_file_get_string(data->kf, "log-custom", n, NULL);
        if(!fmt) return;
 
        /* check for the query first */
-       f = find_add_channel_file(network, l, nick, FALSE);
+       nick = line_get_nick(l);
+       f = find_add_channel_file(data, network, l, nick, FALSE);
 
        if(f) {
                custom_subst(network, &s, fmt, l, nick, FALSE, FALSE);
@@ -393,12 +431,13 @@ static void file_write_channel_query(struct network *network, const char *n, str
        }
        
        nn = find_network_nick(network->state, nick);
+       g_free(nick);
        g_assert(nn);
 
        /* now, loop thru the users' channels */
        for (gl = nn->channel_nicks; gl; gl = gl->next) {
                struct channel_nick *cn = gl->data;
-               f = find_add_channel_file(network, l, cn->channel->name, TRUE);
+               f = find_add_channel_file(data, network, l, cn->channel->name, TRUE);
                if(!f) continue;
 
                custom_subst(network, &s, fmt, l, cn->channel->name, FALSE, FALSE);
@@ -408,14 +447,14 @@ static void file_write_channel_query(struct network *network, const char *n, str
        }
 }
 
-static gboolean log_custom_data(struct network *network, struct line *l, enum data_direction dir, void *userdata)
+static gboolean log_custom_data(struct network *network, const struct line *l, enum data_direction dir, void *userdata)
 {
-       const char *nick = NULL;
-       char *user = NULL;
+    struct log_custom_data *data = userdata;
+       char *nick = NULL;
        if(!l->args || !l->args[0])return TRUE;
 
-       if (l->origin) nick = line_get_nick(l);
-       if(user){ *user = '\0';user++; }
+       if (l->origin) 
+               nick = line_get_nick(l);
 
        /* Loop thru possible values for %@ */
 
@@ -427,32 +466,32 @@ static gboolean log_custom_data(struct network *network, struct line *l, enum da
         */
 
        if(dir == FROM_SERVER && !g_strcasecmp(l->args[0], "JOIN")) {
-               file_write_target(network, "join", l); 
+               file_write_target(data, network, "join", l); 
        } else if(dir == FROM_SERVER && !g_strcasecmp(l->args[0], "PART")) {
-               file_write_channel_only(network, "part", l);
+               file_write_channel_only(data, network, "part", l);
        } else if(!g_strcasecmp(l->args[0], "PRIVMSG")) {
                if(l->args[2][0] == '\ 1') { 
                        l->args[2][strlen(l->args[2])-1] = '\0';
                        if(!g_ascii_strncasecmp(l->args[2], "\ 1ACTION ", 8)) { 
                                l->args[2]+=8;
-                               file_write_target(network, "action", l);
+                               file_write_target(data, network, "action", l);
                                l->args[2]-=8;
                        }
                        l->args[2][strlen(l->args[2])] = '\ 1';
                        /* Ignore all other ctcp messages */
                } else {
-                       file_write_target(network, "msg", l);
+                       file_write_target(data, network, "msg", l);
                }
        } else if(!g_strcasecmp(l->args[0], "NOTICE")) {
-               file_write_target(network, "notice", l);
+               file_write_target(data, network, "notice", l);
        } else if(!g_strcasecmp(l->args[0], "MODE") && l->args[1] && 
                          is_channelname(l->args[1], network->state->info) && dir == FROM_SERVER) {
-               file_write_target(network, "mode", l);
+               file_write_target(data, network, "mode", l);
        } else if(!g_strcasecmp(l->args[0], "QUIT")) {
-               file_write_channel_query(network, "quit", l);
+               file_write_channel_query(data, network, "quit", l);
        } else if(!g_strcasecmp(l->args[0], "KICK") && l->args[1] && l->args[2] && dir == FROM_SERVER) {
                if(!strchr(l->args[1], ',')) {
-                       file_write_channel_only(network, "kick", l);
+                       file_write_channel_only(data, network, "kick", l);
                } else { 
                        char *channels = g_strdup(l->args[1]);
                        char *nicks = g_strdup(l->args[1]);
@@ -467,7 +506,7 @@ static gboolean log_custom_data(struct network *network, struct line *l, enum da
                                if(!n) cont = 0;
                                else *n = '\0';
 
-                               file_write_channel_only(network, "kick", l);
+                               file_write_channel_only(data, network, "kick", l);
 
                                p = n+1;
                                _nick = strchr(_nick, ',');
@@ -479,44 +518,64 @@ static gboolean log_custom_data(struct network *network, struct line *l, enum da
                        g_free(nicks);
                }
        } else if(!g_strcasecmp(l->args[0], "TOPIC") && dir == FROM_SERVER && l->args[1]) {
-               if(l->args[2]) file_write_channel_only(network, "topic", l);
-               else file_write_channel_only(network, "notopic", l);
+               if(l->args[2]) file_write_channel_only(data, network, "topic", l);
+               else file_write_channel_only(data, network, "notopic", l);
        } else if(!g_strcasecmp(l->args[0], "NICK") && dir == FROM_SERVER && l->args[1]) {
-               file_write_channel_query(network, "nickchange", l);
+               file_write_channel_query(data, network, "nickchange", l);
        }
 
+       g_free(nick);
+
        return TRUE;
 }
 
-static gboolean fini_plugin(struct plugin *p)
+static void free_file_info(void *_data)
 {
-       del_log_filter("log_custom");
-       return TRUE;
+       struct file_info *data = _data;
+
+       fclose(data->file);
+       g_free(data);
 }
 
-static gboolean load_config(struct plugin *p, xmlNodePtr node)
+static void load_config(struct global *global)
 {
-       xmlNodePtr cur;
-       
-       for (cur = node->children; cur; cur = cur->next) 
-       {
-               if (cur->type != XML_ELEMENT_NODE) continue;
+       GKeyFile *kf = global->config->keyfile;
+       struct log_custom_data *data;
 
-               if (!strcmp(cur->name, "logfilename")) {
-                       logfilename = xmlNodeGetContent(cur);
-               } else {
-                       g_hash_table_insert(fmts, g_strdup(cur->name), xmlNodeGetContent(cur));
-               }
+       if (!g_key_file_has_group(kf, "log-custom")) {
+               del_log_filter("log_custom");
+               return;
        }
 
+       data = g_new0(struct log_custom_data, 1);
+
+       add_log_filter("log_custom", log_custom_data, data, 1000);
+
+       data->logfilename = g_key_file_get_string(kf, "log-custom", "logfilename", NULL);
+       data->kf = kf;
+}
+
+#define CLEANUP_THRESHOLD (60 * 60 * 24)
+
+static gboolean eval_remove(gpointer key, gpointer value, gpointer user_data)
+{
+       struct file_info *fi = value;
+
+       return (fi->last_used < time(NULL) - CLEANUP_THRESHOLD);
+}
+
+static gboolean cleanup(void *_data)
+{
+       g_hash_table_foreach_remove(files, eval_remove, NULL);
        return TRUE;
 }
 
-static gboolean init_plugin(struct plugin *p)
+static gboolean init_plugin(void)
 {
-       files = g_hash_table_new(g_str_hash, g_str_equal);
-       fmts = g_hash_table_new(g_str_hash, g_str_equal);
-       add_log_filter("log_custom", log_custom_data, NULL, 1000);
+       files = g_hash_table_new_full(g_str_hash, g_str_equal, 
+                       g_free, free_file_info);
+       g_timeout_add(60 * 60, cleanup, NULL);
+       register_load_config_notify(load_config);
        return TRUE;
 }
 
@@ -524,6 +583,4 @@ struct plugin_ops plugin = {
        .name = "log_custom",
        .version = 0,
        .init = init_plugin,
-       .fini = fini_plugin,
-       .load_config = load_config,
 };