* move guile bindings from libmuguile, toys to src/guile:

- don't build a special 'muile' binary anymore, but build as module for
    normal guile
  - remove toys muile / procmule (latter may return at some time)
  - bug fixes
  - however, this is still WIP
This commit is contained in:
djcb
2011-12-01 21:17:23 +02:00
parent c52dfccd7c
commit 1a3fc04e17
24 changed files with 473 additions and 720 deletions

View File

@ -27,17 +27,3 @@ endif
if BUILD_WIDGETS
SUBDIRS += mug2
endif
# for muile, we need guile
if HAVE_GUILE
SUBDIRS += muile
endif
# for procmule, we need guile and gio
if HAVE_GUILE
if HAVE_GIO
SUBDIRS += procmule
endif
endif

View File

@ -1,48 +0,0 @@
## Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
##
## 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
# enforce compiling this dir first before decending into tests/
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= \
muile
muile_SOURCES= \
muile.c \
dummy.cc
# we need to use dummy.cc to enforce c++ linking...
BUILT_SOURCES= \
dummy.cc
dummy.cc:
touch dummy.cc
DISTCLEANFILES= \
$(BUILT_SOURCES)
muile_LDFLAGS= \
${top_builddir}/libmuguile/libmuguile.la

View File

@ -1,205 +0,0 @@
* README
** What is muile?
`muile' is a little experiment/toy using the equally experimental mu guile
bindings, to be found in libmuguile/ in the top-level source directory.
`guile'[1] is an interpreter/library for the Scheme programming language[2],
specifically meant for extending other programs. It is, in fact, the
official GNU language for doing so. 'muile' requires guile 2.x to get the full
support.
Older versions may not support e.g. the 'mu-stats.scm' things discussed below.
The combination of mu + guile is called `muile', and allows you to write
little Scheme-programs to query the mu-database, or inspect individual
messages. It is still in an experimental stage, but useful already.
** How do I get it?
The git-version and the future 0.9.7 version of mu will automatically build
muile if you have guile. I've been using guile 2.x from git, but installing
the 'guile-1.8-dev' package (Ubuntu/Debian) should do the trick. (I only did
very minimal testing with guile 1.8 though).
Then, configure mu. The configure output should tell you about whether guile
was found (and where). If it's found, build mu, and toys/muile should be
created, as well.
** What can I do with it?
Go to toys/muile and start muile. You'll end up with a guile-shell where you
can type scheme [1], it looks something like this (for guile 2.x):
,----
| scheme@(guile-user)>
`----
Now, let's load a message (of course, replace with a message on your system):
,----
| scheme@(guile-user)> (define msg (mu:msg:make-from-file "/home/djcb/Maildir/cur/12131e7b20a2:2,S"))
`----
This defines a variable 'msg', which holds some message on your file
system. It's now easy to inspect this message:
,----
| scheme@(guile-user)> (define msg (mu:msg:make-from-file "/home/djcb/Maildir/cur/12131e7b20a2:2,S"))
`----
Now, we can inspect this message a bit:
,----
| scheme@(guile-user)> (mu:msg:subject msg)
| $1 = "See me in bikini :-)"
| scheme@(guile-user)> (mu:msg:flags msg)
| $2 = (mu:attach mu:unread)
`----
and so on. Note, it's probably easiest to explore the various mu: methods
using autocompletion; to enable that make sure you have
,----
| (use-modules (ice-9 readline))
| (activate-readline)
`----
in your ~/.guile configuration.
** does this tool have some parameters?
Yes, there is --muhome to set a non-default place for the message database
(see the documentation on --muhome in the mu-find manpage).
And there is --msg=<path> where you specify some particular message file;
it will be available as 'mu:current-msg' in the guile (muile) environment. For
example:
,----
| ./muile --msg=~/Maildir/inbox/cur/1311310172_1234:2,S
| [...]
| scheme@(guile-user)> mu:current-msg
| $1 = #<msg /home/djcb/Maildir/inbox/cur/1311310172_1234:2,S>
| scheme@(guile-user)> (mu:msg:size mu:current-msg)
| $2 = 7206
`----
** What about searching messages in the database?
That's easy, too - it does require a little more scheme knowledge. For
searching messages there is the mu:store:for-each function, which takes two
arguments; the first argument is a function that will be called for each
message found. The optional second argument is the search expression (following
'mu find' syntax); if don't provide the argument, all messages match.
So how does this work in practice? Let's see I want to see the subject and
sender for messages about milk:
,----
| (mu:store:for-each (lambda(msg) (format #t "~s ~s\n" (mu:msg:from msg) (mu:msg:subject msg))) "milk")
`----
or slightly more readable:
,----
| (mu:store:for-each
| (lambda(msg)
| (format #t "~s ~s\n" (mu:msg:from msg) (mu:msg:subject msg)))
| "milk")
`----
As you can see, I provide an anonymous ('lambda') function which will be
called for each message matching 'milk'. Admittedly, this requires a bit of
Scheme-knowledge... but this time is good as any to learn this nice
language.
** Can I do some statistics on my messages?
Yes you can. In fact, it's pretty easy. If you load (in the muile/ directory)
the file 'mu-stats.scm':
,----
| (load "mu-stats.scm")
`----
you'll get a bunch of functions (with names starting with 'mu:stats') to make
this very easy. Let's see, suppose I want to see how many messages I get per
weekday:
,----
| scheme@(guile-user)> (mu:stats:per-weekday)
| $1 = ((0 . 2255) (1 . 2788) (2 . 2868) (3 . 2599) (4 . 2629) (5 . 2287) (6 . 1851))
`----
Note, Sunday=0, Monday=1 and so on. Apparently, I get/send most of e-mail on
Tuesdays, and least on Saturday.
And note that mu:stats:per-weekdays takes an optional search expression
argument, to limit the results to messages matching that, e.g., to only
consider messages related to emacs during this year:
,----
| scheme@(guile-user)> (mu:stats:per-weekday "emacs date:2011..now")
| $8 = ((0 . 54) (1 . 22) (2 . 46) (3 . 47) (4 . 39) (5 . 54) (6 . 50))
`----
There's also 'mu:stats:per-month', 'mu:stats:per-year', 'mu:stats:per-hour'.
I learnt that during 3-4am I sent/receive only about a third of what I sent
during 11-12pm.
** What about getting the top-10 people in the To:-field?
Easy.
,----
| scheme@(guile-user)> (mu:stats:top-n-to)
| $1 = ((("Abc" "myself@example.com") . 4465) (("Def" "somebodyelse@example.com") . 2114)
| (and so on)
`----
I've changed the names a bit to protect the innocent, but what the function
does is return a list of pairs of
(<name> <email>) . <frequency>
descending in order of frequency. Note, 'mu:stats:top-n-to' takes two
optional arguments - first the 'n' in top-n (default is 10), and seconds as
search expression to limit the messages considered.
There are also the functions 'mu:stats:top-n-subject' and
'mu:stats:top-n-from' which do the same, mutatis mutandis, and it's quite
easy to add your own (see the mu-stats.scm for examples)
** What about showing the results in a table?
Even easier. Try:
,----
| (mu:stats:table (mu:stats:top-n-to))
`----
or
,----
| (mu:stats:table (mu:stats:per-weekday))
`----
You can also export the table:
,----
| (mu:stats:export (mu:stats:per-weekday))
`----
which will create a temporary file with the results, for further processing
in e.g. 'R' or 'gnuplot'.
[1] http://www.gnu.org/s/guile/
[2] http://en.wikipedia.org/wiki/Scheme_(programming_language)
# Local Variables:
# mode: org; org-startup-folded: nil
# End:

View File

@ -1,218 +0,0 @@
;;
;; Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;;
;; 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.
;; some guile/scheme functions to get various statistics of my mu
;; message store.
(use-modules (ice-9 optargs)) ;; for guile 1.x compatibility
(use-modules (ice-9 popen)) ;; for interfacing with gnuplot
;; note, this is a rather inefficient way to calculate the number; for
;; demonstration purposes only...
(define* (mu:stats:count #:optional (EXPR ""))
"Count the total number of messages. If the optional EXPR is
provided, only count the messages that match it.\n"
(mu:store:for-each (lambda(msg) #f) EXPR))
(define* (mu:stats:average FUNC #:optional (EXPR ""))
"Count the average of the result of applying FUNC on all
messages. If the optional EXPR is provided, only consider the messages
that match it.\n"
(let* ((sum 0)
(n (mu:store:for-each
(lambda(msg) (set! sum (+ sum (FUNC msg)))) EXPR)))
(if (= n 0) 0 (exact->inexact (/ sum n)))))
(define* (mu:stats:average-size #:optional (EXPR ""))
"Calculate the average message size. If the optional EXPR is
provided, only consider the messages that match it.\n"
(mu:stats:average (lambda(msg) (mu:msg:size msg)) EXPR))
(define* (mu:stats:average-recipient-number #:optional (EXPR ""))
"Calculate the average number of recipients (To: + CC: + Bcc:). If
the optional EXPR is provided, only consider the messages that match
it.\n"
(mu:stats:average (lambda(msg)
(+(length (mu:msg:to msg))
(length (mu:msg:cc msg))
(length (mu:msg:bcc msg)))) EXPR))
(define* (mu:stats:frequency FUNC #:optional (EXPR ""))
"FUNC is a function that takes a mMsg, and returns the frequency of
the different values this function returns. If FUNC returns a list,
update the frequency table for each element of this list. If the
optional EXPR is provided, only consider messages that match it.\n"
(let ((table '()))
(mu:store:for-each
(lambda(msg)
;; note, if val is not already a list, turn it into a list
;; then, take frequency for each element in the list
(let* ((val (FUNC msg)) (vals (if (list? val) val (list val))))
(for-each
(lambda (val)
(let ((freq (assoc-ref table val)))
(set! table (assoc-set! table val
(+ 1 (if (eq? freq #f) 0 freq)))))) vals))) EXPR)
table))
(define* (mu:stats:per-weekday #:optional (EXPR ""))
"Count the total number of messages for each weekday (0-6 for
Sun..Sat). If the optional EXPR is provided, only count the messages
that match it. The result is a list of pairs (weekday . frequency).\n"
(let* ((stats (mu:stats:frequency
(lambda (msg) (tm:wday (localtime (mu:msg:date msg)))) EXPR)))
(sort stats (lambda(a b) (< (car a) (car b)))))) ;; in order of weekday
(define* (mu:plot:per-weekday #:optional (EXPR ""))
(let* ((datafile (mu:stats:export (mu:stats:per-weekday EXPR)))
(gnuplot (open-pipe "gnuplot -p" OPEN_WRITE)))
;; note, we cannot use the weekday "%a" support in gnuplot because
;; demands the field to be a date field ('set xdata time' etc.)
;; for that to work, but we cannot use that since gnuplot does not
;; support weekdays ('%w') as a date field in its input
(display (string-append
"reset\n"
"set xtics (\"Sun\" 0, \"Mon\" 1, \"Tue\" 2, \"Wed\" 3,"
"\"Thu\" 4, \"Fri\" 5, \"Sat\" 6);\n"
"set xlabel \"Weekday\"\n"
"set ylabel \"# of messages\"\n"
"set boxwidth 0.9\n") gnuplot)
(display (string-append "plot \"" datafile "\" using 1:2 with boxes fs solid\n")
gnuplot)
(close-pipe gnuplot)))
(define* (mu:stats:per-month #:optional (EXPR ""))
"Count the total number of messages for each month (1-12 for
Jan..Dec). If the optional EXPR is provided, only count the messages
that match it. The result is a list of pairs (month . frequency).\n"
(let* ((stats (mu:stats:frequency
(lambda (msg) ;; note the 1+
(1+ (tm:mon (localtime (mu:msg:date msg))))) EXPR)))
(sort stats
(lambda(a b)
(< (car a) (car b)))))) ;; in order ofmonth
;; (define* (mu:plot:per-month #:optional (EXPR ""))
;; (let* ((data
;; (map ;; add 1 to the month numbers, since gnuplot counts
;; ;; months from 1, not 0
;; (lambda (cell)
;; (cons (1+ (car cell)) (cdr cell)))
;; (mu:stats:per-month EXPR)))
;; (datafile (mu:stats:export data))
;; (gnuplot (open-pipe "gnuplot -p" OPEN_WRITE)))
;; ;; note, we cannot use the weekday "%a" support in gnuplot because
;; ;; demands the field to be a date field ('set xdata time' etc.)
;; ;; for that to work, but we cannot use that since gnuplot does not
;; ;; support weekdays ('%w') as a date field in its input
;; (display (string-append
;; "reset\n"
;; "set xtics (\"Sun\" 0, \"Mon\" 1, \"Tue\" 2, \"Wed\" 3,"
;; "\"Thu\" 4, \"Fri\" 5, \"Sat\" 6);\n"
;; "set xlabel \"Weekday\"\n"
;; "set ylabel \"# of messages\"\n"
;; "set boxwidth 0.9\n") gnuplot)
;; (display (string-append "plot \"" datafile "\" using 1:2 with boxes fs solid\n")
;; gnuplot)
;; (close-pipe gnuplot)))
(define* (mu:stats:per-hour #:optional (EXPR ""))
"Count the total number of messages for each weekday (0-6 for
Sun..Sat). If the optional EXPR is provided, only count the messages
that match it. The result is a list of pairs (weekday . frequency).\n"
(let* ((stats (mu:stats:frequency
(lambda (msg) (tm:hour (localtime (mu:msg:date msg)))) EXPR)))
(sort stats (lambda(a b) (< (car a) (car b)))))) ;; in order of hour
(define* (mu:stats:per-year #:optional (EXPR ""))
"Count the total number of messages for each year since 1970. If the
optional EXPR is provided, only count the messages that match it. The
result is a list of pairs (year . frequency).\n"
(let* ((stats (mu:stats:frequency
(lambda (msg) (+ 1900 (tm:year (localtime (mu:msg:date msg)))))
EXPR)))
(sort stats (lambda(a b) (< (car a) (car b)))))) ;; in order of year
(define* (mu:stats:top-n FUNC N #:optional (EXPR ""))
"Get the Top-N frequency of the result of FUNC applied on each
message. If the optional EXPR is provided, only consider the messages
that match it."
(let* ((freq (mu:stats:frequency FUNC EXPR))
(top (sort freq (lambda (a b) (< (cdr b) (cdr a) )))))
(list-head top (min (length freq) N))))
(define* (mu:stats:top-n-to #:optional (N 10) (EXPR ""))
"Get the Top-N To:-recipients. If the optional N is not provided,
use 10. If the optional EXPR is provided, only consider the messages
that match it."
(mu:stats:top-n
(lambda (msg) (mu:msg:to msg)) N EXPR))
(define* (mu:stats:top-n-from #:optional (N 10) (EXPR ""))
"Get the Top-N senders (From:). If the optional N is not provided,
use 10. If the optional EXPR is provided, only consider the messages
that match it."
(mu:stats:top-n
(lambda (msg) (mu:msg:from msg)) N EXPR))
(define* (mu:stats:top-n-subject #:optional (N 10) (EXPR ""))
"Get the Top-N subjects. If the optional N is not provided,
use 10. If the optional EXPR is provided, only consider the messages
that match it."
(mu:stats:top-n
(lambda (msg) (mu:msg:subject msg)) N EXPR))
(define* (mu:stats:table pairs #:optional (port (current-output-port)))
"Display a list of PAIRS in a table-like fashion."
(let ((maxlen 0))
(for-each ;; find the widest in the first col
(lambda (pair)
(set! maxlen
(max maxlen (string-length (format #f "~s " (car pair)))))) pairs)
(for-each
(lambda (pair)
(let ((first (format #f "~s" (car pair)))
(second (format #f "~s" (cdr pair))))
(display (format #f "~A~v_~A\n"
first (- maxlen (string-length first)) second) port)))
pairs)))
;; (define* (mu:stats:histogram pairs #:optional (port (current-output-port)))
;; "Display a histogram of the list of cons pairs; the car of each pair
;; is used for the x-asxis, while the cdr represents the y value."
;; (let ((pairs ;; pairs may be unsorted, so let's sort first
;; (sort (pairs) (lambda(x1 x2) (< x1 x2)))))
(define (mu:stats:export pairs)
"Export PAIRS to a temporary file, return its name. The data can
then be used in, e.g., R and gnuplot."
(let* ((datafile (tmpnam))
(output (open datafile (logior O_CREAT O_WRONLY) #O0644)))
(mu:stats:table pairs output)
(close output)
datafile))

View File

@ -1,125 +0,0 @@
/*
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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.
**
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <mu-runtime.h>
#include <glib-object.h>
#include <libmuguile/mu-guile-common.h>
#include <libmuguile/mu-guile-msg.h>
struct _MuileConfig {
const char *muhome;
char *msgpath;
};
typedef struct _MuileConfig MuileConfig;
static MuileConfig *
muile_config_new (int *argcp, char ***argvp)
{
GOptionContext *octx;
MuileConfig *opts = g_new0 (MuileConfig, 1);
GOptionEntry entries[] = {
{"muhome", 0, 0, G_OPTION_ARG_FILENAME, &opts->muhome,
"specify an alternative mu directory", NULL},
{"msg", 0, 0, G_OPTION_ARG_FILENAME, &opts->msgpath,
"specify path to a message to load as mu:current-msg)", NULL},
{NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}/* sentinel */
};
octx = g_option_context_new ("- muile options");
g_option_context_add_main_entries (octx, entries, "Muile");
if (!g_option_context_parse (octx, argcp, argvp, NULL)) {
g_option_context_free (octx);
g_printerr ("muile: error in options\n");
return NULL;
}
if (opts->msgpath)
opts->msgpath = mu_util_dir_expand (opts->msgpath);
g_option_context_free (octx);
return opts;
}
static void
muile_config_destroy (MuileConfig *conf)
{
g_free (conf->msgpath);
g_free (conf);
}
static void
usage (void)
{
g_print ("usage: muile [--muhome <dir>] [--msg <msgfile>]\n");
}
int
main (int argc, char *argv[])
{
MuileConfig *opts;
g_type_init ();
#ifdef HAVE_PRE2_GUILE
g_warning ("Note: muile will not function properly unless you are using a"
"UTF-8 locale.");
#endif /* HAVE_PRE2_GUILE */
opts = muile_config_new (&argc, &argv);
if (!opts) {
usage ();
goto error;
}
if (!mu_runtime_init (opts->muhome /* NULL is okay */,
"muile")) {
usage ();
goto error;
}
mu_guile_init (); /* initialize mu guile modules */
if (opts->msgpath) {
if (!(gboolean)scm_with_guile
((MuGuileFunc*)&mu_guile_msg_load_current, opts->msgpath))
goto error;
}
scm_shell (argc, argv);
mu_runtime_uninit ();
muile_config_destroy (opts);
return 0;
error:
muile_config_destroy (opts);
return 1;
}

View File

@ -1,49 +0,0 @@
## Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
##
## 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}

View File

@ -1,32 +0,0 @@
* 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:

View File

@ -1,295 +0,0 @@
/*
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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 <glib.h>
#include <gio/gio.h>
#include <libmuguile/mu-guile-common.h>
#include <libmuguile/mu-guile-msg.h>
#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 */
mu_guile_init (); /* initialize mu guile modules */
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 void
expand_paths (PMConfig *opts)
{
char **cur;
for (cur = opts->watchdirs; cur && *cur; ++cur)
*cur = mu_util_dir_expand (*cur);
if (opts->muhome)
opts->muhome = mu_util_dir_expand (opts->muhome);
}
static PMConfig *
pm_config_new (int *argcp, char ***argvp)
{
GOptionContext *octx;
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=<dir>\n");
goto error;
}
expand_paths (opts);
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=<dir>] [--watch=<dir1>]\n");
g_print ("also, see toys/procmule/README\n");
}
static gboolean
watch_dirs (char **watchdirs)
{
ChildData *child_data;
GSList *watchlist;
GMainLoop *loop;
child_data = child_data_new
(mu_runtime_path(MU_RUNTIME_PATH_MUHOME));
watchlist = create_watchlist (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);
return TRUE;
error:
child_data_destroy (child_data);
return FALSE;
}
int
main (int argc, char *argv[])
{
PMConfig *opts;
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, "procmule")) {
usage ();
goto error;
}
watch_dirs (opts->watchdirs); /* do it! */
mu_runtime_uninit ();
pm_config_destroy (opts);
return 0;
error:
pm_config_destroy (opts);
return 1;
}