From d2029c062fed59707fa1d1e1f7aa1a89c18cd1f6 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sat, 23 Jul 2011 17:57:45 +0300 Subject: [PATCH] * add new toy: procmule, a simple procmail wannabee (WIP) --- toys/Makefile.am | 9 ++ toys/procmule/Makefile.am | 49 +++++++ toys/procmule/README | 32 +++++ toys/procmule/procmule.c | 275 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 365 insertions(+) create mode 100644 toys/procmule/Makefile.am create mode 100644 toys/procmule/README create mode 100644 toys/procmule/procmule.c diff --git a/toys/Makefile.am b/toys/Makefile.am index aac6132f..dd3ddc90 100644 --- a/toys/Makefile.am +++ b/toys/Makefile.am @@ -32,3 +32,12 @@ endif if HAVE_GUILE SUBDIRS += muile endif + +# for procmule, we need guile and gio +if HAVE_GUILE +if HAVE_GIO +SUBDIRS += procmule +endif +endif + + diff --git a/toys/procmule/Makefile.am b/toys/procmule/Makefile.am new file mode 100644 index 00000000..7543376f --- /dev/null +++ b/toys/procmule/Makefile.am @@ -0,0 +1,49 @@ +## Copyright (C) 2011 Dirk-Jan C. Binnema +## +## 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 +## t he 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +include $(top_srcdir)/gtest.mk + +INCLUDES=-I${top_srcdir} -I${top_srcdir}/src ${GUILE_CFLAGS} ${GLIB_CFLAGS} + +# don't use -Werror, as it might break on other compilers +# use -Wno-unused-parameters, because some callbacks may not +# really need all the params they get +AM_CFLAGS=-Wall -Wextra -Wno-unused-parameter -Wdeclaration-after-statement +AM_CXXFLAGS=-Wall -Wextra -Wno-unused-parameter + +noinst_PROGRAMS= \ + procmule + +procmule_SOURCES= \ + procmule.c \ + dummy.cc + +# we need to use dummy.cc to enforce c++ linking... +BUILT_SOURCES= \ + dummy.cc + +dummy.cc: + touch dummy.cc + +# is this needed? +DISTCLEANFILES= \ + $(BUILT_SOURCES) + +procmule_LDFLAGS= \ + ${top_builddir}/libmuguile/libmuguile.la \ + ${top_builddir}/src/libmu.la \ + ${GIO_LIBS} + diff --git a/toys/procmule/README b/toys/procmule/README new file mode 100644 index 00000000..69d9165d --- /dev/null +++ b/toys/procmule/README @@ -0,0 +1,32 @@ +* README + + The toy here is called 'procmule', which tries to offer procmail[1]-like + functionality from mu, with the procmailrc configuration language replaced + with guile and the mu-guile-bindings. + + Of course, I'm not arrogant enough to claim that I can replace a time-tested, + classical Unix tool so easily. On the other hand, I'm gluing existing tools + together. + + Now, the big difference with procmail is that procmail is an MDA - a mail + delivery agent. In practice, it typically reads a new e-mail message from + standard input (it's called from the local mail daemon), and then decides what + to do with it. + + In contrast, procmule watches a directory (or series of directories) for + changes - as soon as a new message appears in one of these directories, + procmule evaluates a Guile/Scheme program (typically, ~/.mu/procmule.scm) with + the message available as 'mu:current-msg'. + + + + +[1] http://www.procmail.org/ + +# Local Variables: +# mode: org; org-startup-folded: nil +# End: + + + + diff --git a/toys/procmule/procmule.c b/toys/procmule/procmule.c new file mode 100644 index 00000000..7215f1cd --- /dev/null +++ b/toys/procmule/procmule.c @@ -0,0 +1,275 @@ +/* +** Copyright (C) 2011 Dirk-Jan C. Binnema +** +** 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include +#include + +#include +#include +#include + +#include "mu-runtime.h" +#include "mu-util.h" + +struct _ChildData { + char **shell_argv; + int shell_argc; +}; +typedef struct _ChildData ChildData; + +static ChildData * +child_data_new (const char *muhome) +{ + ChildData *data; + + data = g_new0 (ChildData, 1); + + data->shell_argv = g_new0 (char*,3); + data->shell_argc = 0; + + data->shell_argv[data->shell_argc++] = g_strdup ("procmule"); + data->shell_argv[data->shell_argc++] = g_strdup ("-s"); + data->shell_argv[data->shell_argc++] = + g_strdup_printf ("%s%cprocmule.scm", muhome, G_DIR_SEPARATOR); + + return data; +} + +static void +child_data_destroy (ChildData *data) +{ + if (!data) + return; + + g_strfreev (data->shell_argv); + g_free (data); +} + +static void +on_dir_change (GFileMonitor *mon, GFile *file, GFile *other_file, + GFileMonitorEvent event_type, ChildData *data) +{ + gchar *path; + + path = g_file_get_path (file); + + /* ignore all except create events */ + if (event_type != G_FILE_MONITOR_EVENT_CREATED) + return; + + if (fork() == 0) { /* run guile in child */ + + scm_with_guile (&mu_guile_msg_init, NULL); + scm_with_guile (&mu_guile_store_init, NULL); + + if (!(gboolean)scm_with_guile + ((MuGuileFunc*)&mu_guile_msg_load_current, path)) { + g_warning ("failed to set message in guile env"); + return; + } + scm_shell (data->shell_argc, data->shell_argv); /* never returns */ + } + + g_free (path); +} + +static GFileMonitor* +create_monitor (const char *path, ChildData *data) +{ + GFile *dir; + GFileMonitor *dirmon; + GError *err; + + if (!mu_util_check_dir (path, TRUE, FALSE)) { + g_warning ("must be a readable dir: '%s'", path); + return NULL; + } + + dir = g_file_new_for_path (path); + + err = NULL; + dirmon = g_file_monitor_directory (dir, G_FILE_MONITOR_NONE, + NULL, &err); + if (!dirmon) { + g_warning ("error adding monitor: %s", err->message); + g_error_free (err); + } + + g_object_unref (dir); + + if (dirmon) + g_signal_connect (dirmon, "changed", + G_CALLBACK(on_dir_change), data); + + return dirmon; +} + +static void +destroy_watchlist (GSList *lst) +{ + g_slist_foreach (lst, (GFunc)g_object_unref, NULL); + g_slist_free (lst); +} + + +GSList* +create_watchlist (char **dirs, ChildData *data) +{ + GSList *watchlist; + char **cur; + + /* TODO: check for dups */ + for (watchlist = NULL, cur = dirs; cur && *cur; ++cur) { + GFileMonitor *dirmon; + dirmon = create_monitor (*cur, data); + if (!dirmon) { + destroy_watchlist (watchlist); + return NULL; + } + watchlist = g_slist_prepend (watchlist, dirmon); + } + + return watchlist; +} + + +struct _PMConfig { + char *muhome; + char **watchdirs; + +}; +typedef struct _PMConfig PMConfig; + + +static PMConfig * +pm_config_new (int *argcp, char ***argvp) +{ + GOptionContext *octx; + char **cur; + PMConfig *opts = g_new0 (PMConfig, 1); + GOptionEntry entries[] = { + {"muhome", 0, 0, G_OPTION_ARG_FILENAME, &opts->muhome, + "specify an alternative mu directory", NULL}, + {"watch", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opts->watchdirs, + "directory to watch (may be specified multiple times)", NULL}, + {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}/* sentinel */ + }; + + octx = g_option_context_new ("- procmule options"); + g_option_context_add_main_entries (octx, entries, "Procmule"); + + if (!g_option_context_parse (octx, argcp, argvp, NULL)) { + g_printerr ("error in options\n"); + goto error; + } + + if (!opts->watchdirs) { + g_printerr ("specify at least one --watch=\n"); + goto error; + } + + for (cur = opts->watchdirs; cur && *cur; ++cur) + *cur = mu_util_dir_expand (*cur); + + if (opts->muhome) + opts->muhome = mu_util_dir_expand (opts->muhome); + + g_option_context_free (octx); + return opts; + +error: + if (octx) + g_option_context_free (octx); + g_free (opts); + return NULL; +} + +static void +pm_config_destroy (PMConfig *conf) +{ + if (!conf) + return; + + g_free (conf->muhome); + g_strfreev (conf->watchdirs); + + g_free (conf); +} + + +static void +usage (void) +{ + g_print ("usage: procmule [--muhome=] [--watch=]\n"); + g_print ("also, see toys/procmule/README\n"); +} + + +int +main (int argc, char *argv[]) +{ + PMConfig *opts; + GSList *watchlist; + GMainLoop *loop; + ChildData *child_data; + + g_type_init (); + g_thread_init (NULL); + +#ifdef HAVE_PRE2_GUILE + g_warning ("Note: pre-2.x version of guile: procmule will not function " + "correctly unless you're using UTF-8 locale."); +#endif /* HAVE_PRE2_GUILE */ + + opts = pm_config_new (&argc, &argv); + if (!opts) { + usage (); + goto error; + } + + if (!mu_runtime_init (opts->muhome /* NULL is okay */, + "procmule")) { + usage (); + goto error; + } + + child_data = child_data_new + (mu_runtime_path(MU_RUNTIME_PATH_MUHOME)); + + watchlist = create_watchlist (opts->watchdirs, child_data); + if (!watchlist) + goto error; + + loop = g_main_loop_new (NULL, TRUE); + g_main_loop_run (loop); + + g_main_loop_unref (loop); + + destroy_watchlist (watchlist); + mu_runtime_uninit (); + pm_config_destroy (opts); + child_data_destroy (child_data); + + return 0; + +error: + pm_config_destroy (opts); + return 1; + +}