emehta-contrib/graphtastical.c

199 lines
5.2 KiB
C

/*
* Copyright (c) 2007 William Pitcock
* Rights to this code are as documented in doc/LICENSE.
*
* Creates a .dot file for use with neato which displays
* user->channel relationships.
*
* == How to generate the graphs and How It Works ==
* Graphtastical creates a .dot file for graphviz's neato
* filter to use. The DOT language describes a graph's
* structure to graphviz in an opaque way.
*
* Because Graphviz nodes use unique identifiers for
* interconnection, the channels.dot file contains also
* information about social networks.
*
* Eventually Graphtastical will dump other graph datafiles
* too.
*
* To make a file from the data dumped by Graphtastical,
* the following commands will do:
*
* $ cat channels.dot | neato -Tgif -o map-channels.gif
* $ cat channels.dot | neato -Tsvg -o map-channels.svg
*
* Some maps (for larger networks) are going to be large,
* so you may want to provide links to both the GIF and
* SVG files as some people may only be able to make use of
* one or the other. Why that is, I'm not sure, and I'm not
* covering it here.
*
* == Privacy concerns ==
* If you are running Graphtastical on a network that has
* privacy concerns; you probably shouldn't.
*/
#include "atheme-compat.h"
DECLARE_MODULE_V1
(
"contrib/graphtastical", true, _modinit, NULL,
PACKAGE_STRING,
"Atheme Development Group <http://www.atheme.org>"
);
static mowgli_eventloop_timer_t *channels_timer = NULL;
static mowgli_eventloop_timer_t *uchannels_timer = NULL;
/* write channels.dot */
static void write_channels_dot_file(void *arg)
{
mychan_t *mc;
chanacs_t *ca;
mowgli_node_t *tn;
FILE *f;
int errno1, was_errored = 0;
mowgli_patricia_iteration_state_t state;
int root = 1;
mychan_t *pmc;
errno = 0;
/* write to a temporary file first */
if (!(f = fopen(DATADIR "/channels.dot.new", "w")))
{
errno1 = errno;
slog(LG_ERROR, "graphtastical: cannot create channels.dot.new: %s", strerror(errno1));
return;
}
fprintf(f, "graph channels {\n");
fprintf(f, "edge [color=blue len=7.5 fontname=\"Verdana\" fontsize=8]\n");
fprintf(f, "node [fontname=\"Verdana\" fontsize=8]\n");
slog(LG_DEBUG, "graphtastical: dumping mychans");
MOWGLI_PATRICIA_FOREACH(mc, &state, mclist)
{
fprintf(f, "\"%s\"", mc->name);
if (!root)
fprintf(f, "-- \"%s\"", pmc->name);
pmc = mc;
fprintf(f, "[fontname=\"Verdana\" fontsize=8]\n");
MOWGLI_ITER_FOREACH(tn, mc->chanacs.head)
{
ca = (chanacs_t *)tn->data;
if (ca->level & CA_AKICK)
continue;
fprintf(f, "\"%s\" -- \"%s\" [fontname=\"Verdana\" fontsize=8]\n", ca->entity ? ca->entity->name : ca->host, mc->name);
}
}
fprintf(f, "}\n");
was_errored = ferror(f);
was_errored |= fclose(f);
if (was_errored)
{
errno1 = errno;
slog(LG_ERROR, "graphtastical: cannot write to channels.dot.new: %s", strerror(errno1));
return;
}
/* now, replace the old database with the new one, using an atomic rename */
if ((srename(DATADIR "/channels.dot.new", DATADIR "/channels.dot")) < 0)
{
errno1 = errno;
slog(LG_ERROR, "graphtastical: cannot rename channels.dot.new to channels.dot: %s", strerror(errno1));
return;
}
}
/* write uchannels.dot */
static void write_uchannels_dot_file(void *arg)
{
channel_t *c;
chanuser_t *cu;
mowgli_node_t *tn;
FILE *f;
int errno1, was_errored = 0;
mowgli_patricia_iteration_state_t state;
errno = 0;
/* write to a temporary file first */
if (!(f = fopen(DATADIR "/uchannels.dot.new", "w")))
{
errno1 = errno;
slog(LG_ERROR, "graphtastical: cannot create channels.dot.new: %s", strerror(errno1));
return;
}
fprintf(f, "graph uchannels {\n");
fprintf(f, "edge [color=blue len=7.5 fontname=\"Verdana\" fontsize=8]\n");
fprintf(f, "node [fontname=\"Verdana\" fontsize=8]\n");
slog(LG_DEBUG, "graphtastical: dumping chans");
MOWGLI_PATRICIA_FOREACH(c, &state, chanlist)
{
fprintf(f, "\"%s\"", c->name);
fprintf(f, "[fontname=\"Verdana\" fontsize=8]\n");
MOWGLI_ITER_FOREACH(tn, c->members.head)
{
cu = (chanuser_t *)tn->data;
fprintf(f, "\"%s\" -- \"%s\" [fontname=\"Verdana\" fontsize=8]\n", cu->user->nick, c->name);
}
}
fprintf(f, "}\n");
was_errored = ferror(f);
was_errored |= fclose(f);
if (was_errored)
{
errno1 = errno;
slog(LG_ERROR, "graphtastical: cannot write to uchannels.dot.new: %s", strerror(errno1));
return;
}
/* now, replace the old database with the new one, using an atomic rename */
if ((srename(DATADIR "/uchannels.dot.new", DATADIR "/uchannels.dot")) < 0)
{
errno1 = errno;
slog(LG_ERROR, "graphtastical: cannot rename uchannels.dot.new to uchannels.dot: %s", strerror(errno1));
return;
}
}
void _modinit(module_t *m)
{
write_channels_dot_file(NULL);
write_uchannels_dot_file(NULL);
channels_timer = mowgli_timer_add(base_eventloop, "write_channels_dot_file", write_channels_dot_file, NULL, 60);
uchannels_timer = mowgli_timer_add(base_eventloop, "write_uchannels_dot_file", write_uchannels_dot_file, NULL, 60);
}
void _moddeinit(module_unload_intent_t intent)
{
mowgli_timer_destroy(base_eventloop, channels_timer);
mowgli_timer_destroy(base_eventloop, uchannels_timer);
}
/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
* vim:ts=8
* vim:sw=8
* vim:noexpandtab
*/