Move linestack to libirc.
[jelmer/ctrlproxy.git] / libirc / linestack.c
diff --git a/libirc/linestack.c b/libirc/linestack.c
new file mode 100644 (file)
index 0000000..0affc6d
--- /dev/null
@@ -0,0 +1,355 @@
+/* 
+       ctrlproxy: A modular IRC proxy
+       (c) 2002-2003 Jelmer Vernooij <jelmer@nl.linux.org>
+
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 3 of the License, or
+       (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "internals.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include "irc.h"
+
+struct linestack_context *create_linestack(const char *name, 
+                                                                                  gboolean truncate,
+                                                                                  const char *basedir,
+                                                                                  const struct irc_network_state *state)
+{
+       struct linestack_context *ctx;
+
+       g_assert(name);
+       g_assert(state);
+
+       ctx = g_new0(struct linestack_context, 1);
+       ctx->ops = &linestack_file;
+       ctx->ops->init(ctx, name, truncate, basedir, state);
+
+       return ctx;
+}
+
+void free_linestack_context(struct linestack_context *ctx)
+{
+       if (ctx)
+               ctx->ops->fini(ctx);
+
+       g_free(ctx);
+}
+
+static struct linestack_marker *wrap_linestack_marker(struct linestack_context *ctx, void *data)
+{
+       struct linestack_marker *mrk;
+       if (data == NULL)
+               return NULL;
+
+       mrk = g_new0(struct linestack_marker, 1);
+       mrk->free_fn = ctx->ops->free_marker;
+       mrk->data = data;
+       return mrk;
+}
+
+struct linestack_marker *linestack_get_marker_numlines (struct linestack_context *ctx, int lines)
+{
+       g_assert(ctx != NULL);
+       if (ctx->ops == NULL) 
+               return NULL;
+
+       if (ctx->ops->get_marker_numlines == NULL) 
+               return NULL;
+
+       return wrap_linestack_marker(ctx, ctx->ops->get_marker_numlines(ctx, lines));
+}
+
+struct irc_network_state *linestack_get_state(
+               struct linestack_context *ctx, struct linestack_marker *lm)
+{
+       struct irc_network_state *st;
+       g_assert(ctx != NULL);
+
+       if (!ctx->ops) return NULL;
+       if (!ctx->ops->get_state) return NULL;
+
+       st = ctx->ops->get_state(ctx, lm?lm->data:NULL);
+       if (st == NULL)
+               return NULL;
+
+       g_assert(st->me.nick);
+       g_assert(st->me.query);
+       return st;
+}
+
+gboolean linestack_traverse(struct linestack_context *ctx,                                     
+               struct linestack_marker *lm_from, struct linestack_marker *lm_to,
+               linestack_traverse_fn handler, void *userdata)
+{
+       g_assert(ctx != NULL);
+       g_assert(ctx->ops != NULL);
+       g_assert(ctx->ops->traverse != NULL);
+
+       return ctx->ops->traverse(ctx, lm_from?lm_from->data:NULL, lm_to?lm_to->data:NULL, handler, userdata);
+}
+
+struct traverse_object_data {
+       linestack_traverse_fn handler;
+       const char *object;
+       void *userdata;
+};
+
+static gboolean traverse_object_handler(struct irc_line *l, time_t t, void *state)
+{
+       struct traverse_object_data *d = state;
+       gboolean ret = TRUE;
+
+       if (l->argc < 2) 
+               return TRUE;
+
+       if (strchr(l->args[1], ',') == NULL) {
+               if (!strcmp(l->args[1], d->object))
+                       ret &= d->handler(l, t, d->userdata);
+       } else {
+               int i;
+               char **channels = g_strsplit(l->args[1], ",", 0);
+               for (i = 0; channels[i]; i++) {
+                       if (!strcmp(channels[i], d->object)) {
+                               gboolean ret = d->handler(l, t, d->userdata);
+                               g_strfreev(channels);
+                               return ret;
+                       }
+               }
+               g_strfreev(channels);
+       }
+
+       return ret;
+}
+
+gboolean linestack_traverse_object(
+                       struct linestack_context *ctx,
+                       const char *obj, 
+                       struct linestack_marker *lm_from, 
+                       struct linestack_marker *lm_to, linestack_traverse_fn hl,
+                       void *userdata)
+{
+       struct traverse_object_data d;
+       g_assert(ctx != NULL);
+       g_assert(ctx->ops != NULL);
+
+       d.object = obj;
+       d.userdata = userdata;
+       d.handler = hl;
+       
+       return linestack_traverse(ctx, lm_from, lm_to, traverse_object_handler, &d);
+}
+
+void linestack_free_marker(struct linestack_marker *lm)
+{
+       if (lm == NULL)
+               return;
+
+       if (lm->free_fn != NULL) 
+               lm->free_fn(lm->data);
+       g_free(lm);
+}
+
+struct linestack_marker *linestack_get_marker(struct linestack_context *ctx)
+{
+       if (ctx == NULL)
+               return NULL;
+
+       g_assert (ctx->ops != NULL);
+       g_assert (ctx->ops->get_marker != NULL);
+
+       return wrap_linestack_marker(ctx, ctx->ops->get_marker(ctx));
+}
+
+static const char *linestack_messages[] = { 
+       "NICK", "JOIN", "QUIT", "PART", "PRIVMSG", "NOTICE", "KICK", 
+       "MODE", "TOPIC", 
+       "353", /* RPL_NAMREPLY */
+       "366", /* RPL_ENDOFNAMES */
+       "331", /* RPL_NOTOPIC */
+       "333", /* RPL_TOPICWHOTIME */
+       "332", /* RPL_TOPIC */
+       "324", /* RPL_CHANNELMODEIS */
+       "329", /* RPL_CREATIONTIME */
+       NULL };
+
+gboolean linestack_insert_line(struct linestack_context *ctx, 
+                                                          const struct irc_line *l, enum data_direction dir, 
+                                                          const struct irc_network_state *state)
+{
+       int i;
+       gboolean needed = FALSE;
+
+       if (ctx == NULL) return TRUE;
+
+       if (l->argc == 0) return TRUE;
+
+       if (!ctx->ops) return TRUE;
+       g_assert(ctx->ops->insert_line);
+
+       /* Only need PRIVMSG and NOTICE messages we send ourselves */
+       if (dir == TO_SERVER && 
+               g_strcasecmp(l->args[0], "PRIVMSG") && 
+               g_strcasecmp(l->args[0], "NOTICE")) return TRUE;
+
+       /* No CTCP, please */
+       if ((!g_strcasecmp(l->args[0], "PRIVMSG") ||
+               !g_strcasecmp(l->args[0], "NOTICE")) && 
+               l->argc > 2 && l->args[2][0] == '\001' && 
+               g_strncasecmp(l->args[2], "\001ACTION", 7) != 0)
+               return TRUE;
+
+       for (i = 0; linestack_messages[i]; i++) 
+               if (!g_strcasecmp(linestack_messages[i], l->args[0]))
+                       needed = TRUE;
+
+       if (!needed) return TRUE;
+
+       for (i = 0; i < l->argc; i++) {
+               g_assert(strchr(l->args[i], '\n') == NULL);
+               g_assert(strchr(l->args[i], '\r') == NULL);
+       }
+
+       return ctx->ops->insert_line(ctx, l, state);
+}
+
+struct send_line_privdata {
+       int time_offset;
+       struct irc_client *client;
+};
+
+static gboolean send_line(struct irc_line *l, time_t t, void *_privdata)
+{
+       struct send_line_privdata *privdata = _privdata;
+       return client_send_line(privdata->client, l, NULL);
+}
+
+static gboolean send_line_timed(struct irc_line *l, time_t t, void *_privdata)
+{
+       struct send_line_privdata *privdata = _privdata;
+
+       if ((!g_strcasecmp(l->args[0], "PRIVMSG") ||
+               !g_strcasecmp(l->args[0], "NOTICE")) &&
+               l->argc > 2) {
+               struct irc_line *nl = line_prefix_time(l, t+privdata->time_offset);
+               gboolean ret;
+               ret = client_send_line(privdata->client, nl, NULL);
+               free_line(nl);
+               return ret;
+       } else {
+               return client_send_line(privdata->client, l, NULL);
+       }
+}
+
+static gboolean send_line_timed_dataonly(struct irc_line *l, time_t t, void *_privdata)
+{
+       struct send_line_privdata *privdata = _privdata;
+       gboolean ret;
+       struct irc_line *nl;
+
+       if (g_strcasecmp(l->args[0], "PRIVMSG") != 0 && 
+               g_strcasecmp(l->args[0], "NOTICE") != 0)
+               return TRUE;
+
+       if (l->argc <= 2)
+               return TRUE;
+
+       nl = line_prefix_time(l, t+privdata->time_offset);
+       ret = client_send_line(privdata->client, nl, NULL);
+       free_line(nl);
+       return ret;
+}
+
+static gboolean send_line_dataonly(struct irc_line *l, time_t t, void *_privdata)
+{
+       struct send_line_privdata *privdata = _privdata;
+
+       if (g_strcasecmp(l->args[0], "PRIVMSG") != 0 && 
+               g_strcasecmp(l->args[0], "NOTICE") != 0)
+               return TRUE;
+
+       if (l->argc <= 2)
+               return TRUE;
+
+       return client_send_line(privdata->client, l, NULL);
+}
+
+gboolean linestack_send(struct linestack_context *ctx, struct linestack_marker *mf, struct linestack_marker *mt, struct irc_client *c, gboolean dataonly, gboolean timed, int time_offset)
+{
+       struct send_line_privdata privdata;
+       linestack_traverse_fn trav_fn;
+
+       privdata.client = c;
+       privdata.time_offset = time_offset;
+
+       if (dataonly) {
+               if (timed) 
+                       trav_fn = send_line_timed_dataonly;
+               else 
+                       trav_fn = send_line_dataonly;
+       } else {
+               if (timed)
+                       trav_fn = send_line_timed;
+               else
+                       trav_fn = send_line;
+       }
+
+       return linestack_traverse(ctx, mf, mt, trav_fn, &privdata);
+}
+
+gboolean linestack_send_object(struct linestack_context *ctx, const char *obj, struct linestack_marker *mf, struct linestack_marker *mt, struct irc_client *c, gboolean dataonly, gboolean timed, int time_offset)
+{
+       struct send_line_privdata privdata;
+       linestack_traverse_fn trav_fn;
+
+       privdata.client = c;
+       privdata.time_offset = time_offset;
+
+       if (dataonly) {
+               if (timed) 
+                       trav_fn = send_line_timed_dataonly;
+               else 
+                       trav_fn = send_line_dataonly;
+       } else {
+               if (timed)
+                       trav_fn = send_line_timed;
+               else
+                       trav_fn = send_line;
+       }
+
+       return linestack_traverse_object(ctx, obj, mf, mt, trav_fn, &privdata);
+}
+
+static gboolean replay_line(struct irc_line *l, time_t t, void *state)
+{
+       struct irc_network_state *st = state;
+       state_handle_data(st, l);
+       return TRUE;
+}
+
+gboolean linestack_replay(struct linestack_context *ctx, 
+                                                 struct linestack_marker *mf, 
+                                                 struct linestack_marker *mt, 
+                                                 struct irc_network_state *st)
+{
+       return linestack_traverse(ctx, mf, mt, replay_line, st);
+}
+
+struct linestack_context *new_linestack(struct irc_network *n, const char *basedir)
+{
+       return create_linestack(n->name, TRUE, basedir, n->external_state);
+}