+$Id$
+
Jon Steinhart's (jon@fourwinds.com) Attachment Handling Mods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- /dev/null
+$Id$
+
+Jon Steinhart's (jon@fourwinds.com) External Program Hooks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This blurb describes a changes to nmh that implement an interface
+to external programs. This interface is different than the limited
+interface provided by things like the rmmproc context entry.
+
+
+Why Did I Do This?
+~~~~~~~~~~~~~~~~~~
+
+I'm working on a project (grokmail) that will get released via GPL sometime
+soon. This project keeps a database synchronized with the messages in the
+mail system. New functionality is built on top of this database. This
+functionality allows fast searching, and searching based on interest
+criteria. The latter can be used for spam filtering.
+
+The changes to nmh allow external programs to be run whenever a message is
+added to a folder, removed from a folder, or refiled. The changes are
+implemented in a general way so that it can be used for other purposes than
+mine.
+
+What Are The Changes?
+~~~~~~~~~~~~~~~~~~~~~
+
+The changes add four new profile components:
+
+add-hook: This is the full pathname of a program that is invoked
+ whenever a message is added to a folder. The program
+ is passed one argument which is the full pathname of the
+ message file. The program is executed after the message
+ is written so that it can act upon that message.
+
+del-hook: This is the full pathname of a program that is invoked
+ whenever a message is deleted from a folder. The program
+ is passed one argument which is the full pathname of the
+ message file. The program is executed before the message
+ is written so that it can act upon that message.
+
+ref-hook: This is the full pathname of a program that is invoked
+ whenever a message is refiled. The program is passed two
+ arguments: the first is the full pathname of the original
+ message file, the second is the full pathname of the final
+ message file. The program is executed after the message
+ is written.
+
+msg-hook: This is a text message that is output if the execution of
+ one of the external hook programs fails. There is a built-in
+ default message if none is specified.
+
+The definition of refiling is a bit tricky. The refile hook is executed if a
+message is moved from one place to another. So, for example, the command
+
+ refile -link
+
+causes the add hook to be executed, not the refile hook, because a new message
+is created, the old one isn't moved.
+
+These changes affect the following commands:
+
+burst: The add hook is executed for messages burst from a digest, and
+ for the table of contents if -inplace is specified. The delete
+ hook is executed for the original message if -inplace is
+ specified. The refile hook is executed for messages that are
+ moved.
+
+folder: The refile hook is executed for -pack.
+
+inc: The add hook is executed when messages are incorporated.
+
+refile: Either the add or refile hooks are executed.
+
+rmf: The delete hook is executed when messages are deleted.
+
+rmm: The delete hook is executed when messages are deleted.
+
+sortm: The refile hook is executed for each message moved. Note that
+ a magic temporary message number of 2147483647 is used to hold
+ messages as they are being shuffled.
+
+
+
+Did I Do This Correctly?
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Well, sort of. This all works, but I'm not really happy with it. The issue
+is that an examination of the nmh code shows that message handling is scattered
+all over the place. Although there are library routines such as folder_addmsg
+and folder_delmsgs, they are not used consistently. Long term, I think that it
+would be better to make all message handling go through the same choke points.
+
+Also, I added a function to run the external programs. This sort of stuff is
+also scattered around the nmh code, for example in the code to run the rmmproc.
+Again, I'd like to make this more consistent in the long term.
+
+What Files Did I Change?
+~~~~~~~~~~~~~~~~~~~~~~~~
+uip/
+ burst.c
+ inc.c
+ refile.c
+ rmf.c
+ sortm.c
+
+sbr/
+ folder_addmsg.c
+ folder_delmsgs.c
+ folder_pack.c
+ ext_hook.c (new file)
int decode_rfc2047 (char *, char *);
void discard (FILE *);
int done (int);
+int ext_hook(char *, char *, char *);
int fdcompare (int, int);
-int folder_addmsg (struct msgs **, char *, int, int, int);
-int folder_delmsgs (struct msgs *, int);
+int folder_addmsg (struct msgs **, char *, int, int, int, int);
+int folder_delmsgs (struct msgs *, int, int);
void folder_free (struct msgs *);
int folder_pack (struct msgs **, int);
struct msgs *folder_read (char *);
context_find.c context_foil.c context_read.c \
context_replace.c context_save.c copy.c \
copyip.c cpydata.c cpydgst.c discard.c done.c dtime.c dtimep.c \
- error.c fdcompare.c folder_addmsg.c folder_delmsgs.c \
+ error.c ext_hook.o fdcompare.c folder_addmsg.c folder_delmsgs.c \
folder_free.c folder_pack.c folder_read.c \
folder_realloc.c gans.c getans.c getanswer.c \
getarguments.c getcpy.c getfolder.c getpass.c \
int
folder_addmsg (struct msgs **mpp, char *msgfile, int selected,
- int unseen, int preserve)
+ int unseen, int preserve, int deleting)
{
int infd, outfd, linkerr, first_time, msgnum;
char *nmsg, newmsg[BUFSIZ];
+ char oldmsg[BUFSIZ];
+ struct msgs *op;
struct msgs *mp;
struct stat st1, st2;
snprintf (newmsg, sizeof(newmsg), "%s/%s", mp->foldpath, nmsg);
/*
- * Now try to link message into folder
+ * Now try to link message into folder.
+ * Then run the external hook on the message if one was specified in the context.
+ * Run the refile hook if we're moving the message from one place to another.
+ * We have to construct the from path name for this because it's not there.
+ * Run the add hook if the message is getting copied or lined somewhere else.
*/
if (link (msgfile, newmsg) != -1) {
+
+ if (deleting) {
+ op = folder_read(getfolder(1));
+ (void)snprintf(oldmsg, sizeof (oldmsg), "%s/%s", op->foldpath, msgfile);
+ folder_free(op);
+ (void)ext_hook("ref-hook", oldmsg, newmsg);
+ }
+ else
+ (void)ext_hook("add-hook", newmsg, (char *)0);
+
return msgnum;
} else {
linkerr = errno;
cpydata (infd, outfd, msgfile, newmsg);
close (infd);
close (outfd);
+
+ if (deleting)
+ (void)ext_hook("ref-hook", newmsg, msgfile);
+ else
+ (void)ext_hook("add-hook", newmsg, (char *)0);
+
return msgnum;
}
}
*/
int
-folder_delmsgs (struct msgs *mp, int unlink_msgs)
+folder_delmsgs (struct msgs *mp, int unlink_msgs, int nohook)
{
pid_t pid;
int msgnum, vecp, retval = 0;
char buf[100], *dp, **vec;
+ char msgpath[BUFSIZ];
/*
* If "rmmproc" is defined, exec it to remove messages.
unset_selected (mp, msgnum);
mp->numsel--;
+ /*
+ * Run the external hook on the message if one was specified in the context.
+ * All we have is the message number; we have changed to the directory
+ * containing the message. So, we need to extract that directory to form
+ * the complete path. Note that the caller knows the directory, but has
+ * no way of passing that to us.
+ */
+
+ if (!nohook) {
+ (void)snprintf(msgpath, sizeof (msgpath), "%s/%d", getcwd(msgpath, sizeof (msgpath)), msgnum);
+ (void)ext_hook("del-hook", msgpath, (char *)0);
+ }
+
dp = m_name (msgnum);
if (unlink_msgs) {
return -1;
}
+ /*
+ * Invoke the external refile hook for each message being renamed.
+ */
+
+ (void)snprintf(oldmsg, sizeof (oldmsg), "%s/%d", mp->foldpath, msgnum);
+ (void)snprintf(newmsg, sizeof (newmsg), "%s/%d", mp->foldpath, hole);
+ ext_hook("ref-hook", oldmsg, newmsg);
+
/* check if this is the current message */
if (msgnum == mp->curmsg)
newcurrent = hole;
/*
* Convert the various message names to
- * there numeric value.
+ * their numeric values.
*
* n (integer)
* prev
* static prototypes
*/
static int find_delim (int, struct smsg *);
-static void burst (struct msgs **, int, struct smsg *, int, int, int);
+static void burst (struct msgs **, int, struct smsg *, int, int, int, char *);
static void cpybrst (FILE *, FILE *, char *, char *, int);
if (verbosw)
printf ("%d message%s exploded from digest %d\n",
numburst, numburst > 1 ? "s" : "", msgnum);
- burst (&mp, msgnum, smsgs, numburst, inplace, verbosw);
+ burst (&mp, msgnum, smsgs, numburst, inplace, verbosw, maildir);
} else {
if (numburst == 0) {
if (!quietsw)
static void
burst (struct msgs **mpp, int msgnum, struct smsg *smsgs, int numburst,
- int inplace, int verbosw)
+ int inplace, int verbosw, char *maildir)
{
int i, j, mode;
char *msgnam;
* If -inplace is given, renumber the messages after the
* source message, to make room for each of the messages
* contained within the digest.
+ *
+ * This is equivalent to refiling a message from the point
+ * of view of the external hooks.
*/
if (inplace) {
for (i = mp->hghmsg; j > msgnum; i--, j--) {
if (rename (f2, f1) == NOTOK)
admonish (f1, "unable to rename %s to", f2);
+
+ (void)snprintf(f1, sizeof (f1), "%s/%d", maildir, i);
+ (void)snprintf(f2, sizeof (f2), "%s/%d", maildir, j);
+ ext_hook("ref-hook", f1, f2);
+
copy_msg_flags (mp, i, j);
clear_msg_flags (mp, j);
mp->msgflags |= SEQMOD;
unset_selected (mp, msgnum);
- /* new hghmsg is hghmsg + numburst */
+ /* new hghmsg is hghmsg + numburst
+ *
+ * At this point, there is an array of numburst smsgs, each element of
+ * which contains the starting and stopping offsets (seeks) of the message
+ * in the digest. The inplace flag is set if the original digest is replaced
+ * by a message containing the table of contents. smsgs[0] is that table of
+ * contents. Go through the message numbers in reverse order (high to low).
+ *
+ * Set f1 to the name of the destination message, f2 to the name of a scratch
+ * file. Extract a message from the digest to the scratch file. Move the
+ * original message to a backup file if the destination message number is the
+ * same as the number of the original message, which only happens if the
+ * inplace flag is set. Then move the scratch file to the destination message.
+ *
+ * Moving the original message to the backup file is equivalent to deleting the
+ * message from the point of view of the external hooks. And bursting each
+ * message is equivalent to adding a new message.
+ */
+
i = inplace ? msgnum + numburst : mp->hghmsg;
for (j = numburst; j >= (inplace ? 0 : 1); i--, j--) {
strncpy (f1, m_name (i), sizeof(f1));
strncpy (f3, m_backup (f1), sizeof(f3));
if (rename (f1, f3) == NOTOK)
admonish (f3, "unable to rename %s to", f1);
+
+ (void)snprintf(f3, sizeof (f3), "%s/%d", maildir, i);
+ ext_hook("del-hook", f3, (char *)0);
}
if (rename (f2, f1) == NOTOK)
admonish (f1, "unable to rename %s to", f2);
+
+ (void)snprintf(f3, sizeof (f3), "%s/%d", maildir, i);
+ ext_hook("add-hook", f3, (char *)0);
+
copy_msg_flags (mp, i, msgnum);
mp->msgflags |= SEQMOD;
}
struct msgs *mp;
struct stat st, s1;
FILE *aud = NULL;
+ char b[MAXPATHLEN + 1];
#ifdef POP
int nmsgs, nbytes, p = 0;
/* link message into folder */
newmsg = folder_addmsg(mp, tmpfilenam);
#endif
-
/* create scanline for new message */
switch (i = scan (in, msgnum + 1, msgnum + 1, nfs, width,
msgnum == hghnum && chgflag, 1, NULL, 0L, noisy)) {
case SCNMSG:
case SCNENC:
+ /*
+ * Run the external program hook on the message.
+ */
+
+ (void)snprintf(b, sizeof (b), "%s/%d", maildir, msgnum + 1);
+ (void)ext_hook("add-hook", b, (char *)0);
+
if (aud)
fputs (scanl, aud);
#ifdef MHE
/* Read the folder. */
if ((mp = folder_read (folder))) {
/* Link file into folder */
- msgnum = folder_addmsg (&mp, filename, 0, 0, 0);
+ msgnum = folder_addmsg (&mp, filename, 0, 0, 0, 0);
} else {
advise (NULL, "unable to read folder %s", folder);
return NOTOK;
* Link message into folder, and possibly add
* to the Unseen-Sequence's.
*/
- if ((msgnum = folder_addmsg (&mp, tmpfilenam, 0, unseensw, 0)) == -1)
+ if ((msgnum = folder_addmsg (&mp, tmpfilenam, 0, unseensw, 0, 0)) == -1)
done (1);
/*
static void opnfolds (struct st_fold *, int);
static void clsfolds (struct st_fold *, int);
static void remove_files (int, char **);
-static int m_file (char *, struct st_fold *, int, int);
+static int m_file (char *, struct st_fold *, int, int, int);
int
adios (NULL, "use -file or some messages, not both");
opnfolds (folders, foldp);
for (i = 0; i < filep; i++)
- if (m_file (files[i], folders, foldp, preserve))
+ if (m_file (files[i], folders, foldp, preserve, 0))
done (1);
/* If -nolink, then "remove" files */
if (!linkf)
/* create folder structures for each destination folder */
opnfolds (folders, foldp);
- /* Link all the selected messages into destination folders */
+ /* Link all the selected messages into destination folders.
+ *
+ * This causes the add hook to be run for messages that are
+ * linked into another folder. The refile hook is run for
+ * messages that are moved to another folder.
+ */
for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
if (is_selected (mp, msgnum)) {
cp = getcpy (m_name (msgnum));
- if (m_file (cp, folders, foldp, preserve))
+ if (m_file (cp, folders, foldp, preserve, !linkf))
done (1);
free (cp);
}
fflush (stdout);
}
- /* If -nolink, then "remove" messages from source folder */
+ /* If -nolink, then "remove" messages from source folder.
+ *
+ * Note that folder_delmsgs does not call the delete hook
+ * because the message has already been handled above.
+ */
if (!linkf) {
- folder_delmsgs (mp, unlink_msgs);
+ folder_delmsgs (mp, unlink_msgs, 1);
}
clsfolds (folders, foldp);
*/
static int
-m_file (char *msgfile, struct st_fold *folders, int nfolders, int preserve)
+m_file (char *msgfile, struct st_fold *folders, int nfolders, int preserve, int refile)
{
int msgnum;
struct st_fold *fp, *ep;
for (fp = folders, ep = folders + nfolders; fp < ep; fp++) {
- if ((msgnum = folder_addmsg (&fp->f_mp, msgfile, 1, 0, preserve)) == -1)
+ if ((msgnum = folder_addmsg (&fp->f_mp, msgfile, 1, 0, preserve, nfolders == 1 && refile)) == -1)
return 1;
}
return 0;
adios (NULL, "unable to read folder +%s", folder);
others = 0;
+ /*
+ * Run the external delete hook program.
+ */
+
+ (void)ext_hook("del-hook", maildir, (char *)0);
+
j = strlen(BACKUP_PREFIX);
while ((dp = readdir (dd))) {
switch (dp->d_name[0]) {
}
/* "remove" the SELECTED messages */
- folder_delmsgs (mp, unlink_msgs);
+ folder_delmsgs (mp, unlink_msgs, 0);
seq_save (mp); /* synchronize message sequences */
context_replace (pfolder, folder); /* update current folder */
if (verbose) { /* announce what we're doing */
if (subjsort)
- printf ("sorting by %s-major %s-minor\n",
+ printf ("sorting by %s-major %s-minor\n",
submajor ? subjsort : datesw,
submajor ? datesw : subjsort);
else
}
/* first sort by date, or by subject-major, date-minor */
- qsort ((char *) dlist, nmsgs, sizeof(*dlist),
+ qsort ((char *) dlist, nmsgs, sizeof(*dlist),
(qsort_comp) (submajor && subjsort ? txtsort : dsort));
/*
*/
while (*s && (*s)->s_subj[0] &&
strcmp((*s)->s_subj, s[-1]->s_subj) == 0 &&
- (datelimit == 0 ||
+ (datelimit == 0 ||
(*s)->s_clock - s[-1]->s_clock <= datelimit)) {
il[(*s)->s_msg] = 0;
*fp++ = *s++;
free (dlist);
dlist = flist;
}
+
+ /*
+ * At this point, dlist is a sorted array of pointers to smsg structures,
+ * each of which contains a message number.
+ */
+
rename_msgs (mp, dlist);
context_replace (pfolder, folder); /* update current folder */
return done (0);
}
-static int
+static int
read_hdrs (struct msgs *mp, char *datesw)
{
int msgnum;
/*
* try to make the subject "canonical": delete
* leading "re:", everything but letters & smash
- * letters to lower case.
+ * letters to lower case.
*/
register char *cp, *cp2, c;
/*
* sort on dates.
*/
-static int
+static int
dsort (struct smsg **a, struct smsg **b)
{
if ((*a)->s_clock < (*b)->s_clock)
/*
* sort on subjects.
*/
-static int
+static int
subsort (struct smsg **a, struct smsg **b)
{
register int i;
return (dsort (a, b));
}
-static int
+static int
txtsort (struct smsg **a, struct smsg **b)
{
register int i;
{
int nxt, old, new;
char *newname, oldname[BUFSIZ];
+ char newbuf[MAXPATHLEN + 1];
for (;;) {
nxt = mlist[msg] - smsgs; /* mlist[msg] is a ptr into smsgs */
if (rename (oldname, newname) == NOTOK)
adios (newname, "unable to rename %s to", oldname);
+ (void)snprintf(oldname, sizeof (oldname), "%s/%d", mp->foldpath, old);
+ (void)snprintf(newbuf, sizeof (newbuf), "%s/%d", mp->foldpath, new);
+ ext_hook("ref-hook", oldname, newbuf);
+
copy_msg_flags (mp, new, old);
if (mp->curmsg == old)
seq_setcur (mp, new);
- if (nxt == endmsg)
+ if (nxt == endmsg)
break;
msg = nxt;
int i, j, old, new;
seqset_t tmpset;
char f1[BUFSIZ], tmpfil[BUFSIZ];
+ char newbuf[MAXPATHLEN + 1];
struct smsg *sp;
strncpy (tmpfil, m_name (mp->hghmsg + 1), sizeof(tmpfil));
for (i = 0; i < nmsgs; i++) {
- if (! (sp = mlist[i]))
+ if (! (sp = mlist[i]))
continue; /* did this one */
j = sp - smsgs;
if (rename (f1, tmpfil) == NOTOK)
adios (tmpfil, "unable to rename %s to ", f1);
+
+ /*
+ * Run the external hook to refile the old message as message
+ * number 2147483647. This is our way of making a temporary
+ * message number. I don't really like this.
+ */
+
+ (void)snprintf(f1, sizeof (f1), "%s/%d", mp->foldpath, old);
+ (void)snprintf(newbuf, sizeof (newbuf), "%s/2147483647", mp->foldpath);
+ ext_hook("ref-hook", f1, newbuf);
+
get_msg_flags (mp, &tmpset, old);
rename_chain (mp, mlist, j, i);
if (rename (tmpfil, m_name(new)) == NOTOK)
adios (m_name(new), "unable to rename %s to", tmpfil);
+ /*
+ * Run the external hook to refile the temorary message number
+ * to the real place.
+ */
+
+ (void)snprintf(f1, sizeof (f1), "%s/%d", mp->foldpath, new);
+ ext_hook("ref-hook", newbuf, f1);
+
set_msg_flags (mp, &tmpset, new);
mp->msgflags |= SEQMOD;
}