From: Stephen Gildea Date: Mon, 17 Feb 2020 19:02:49 +0000 (-0800) Subject: patch: Maildir support for "scan -file" X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/f8437b262c94ef12c57f6024aaaab73f7ae50cf8?ds=sidebyside;hp=b18f2d1fcd634e79fff9d69bc9b7ddf220755290 patch: Maildir support for "scan -file" I have extended the "scan -file" command line so that the "file" can also be a Maildir directory. This feature complements the Maildir support added to the inc command in 1.4. Just as you may want to inc your mail from Maildir, you might want to see what awaits you with a scan first. Signed-off-by: David Levine --- diff --git a/Makefile.am b/Makefile.am index ffc4d65b..329a762a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -150,6 +150,7 @@ TESTS = \ test/repl/test-trailing-newline \ test/scan/test-header-parsing \ test/scan/test-scan \ + test/scan/test-scan-file \ test/scan/test-scan-multibyte \ test/send/test-sendfrom \ test/sequences/test-flist \ @@ -403,6 +404,7 @@ noinst_HEADERS = \ sbr/m_name.h \ sbr/m_popen.h \ sbr/m_rand.h \ + sbr/maildir_read_and_sort.h \ sbr/makedir.h \ sbr/message_id.h \ sbr/mime_type.h \ @@ -1120,6 +1122,7 @@ sbr_libmh_a_SOURCES = \ sbr/m_name.c \ sbr/m_popen.c \ sbr/m_rand.c \ + sbr/maildir_read_and_sort.c \ sbr/makedir.c \ sbr/message_id.c \ sbr/mf.c \ diff --git a/docs/pending-release-notes b/docs/pending-release-notes index 5a932160..a2c415c9 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -23,6 +23,7 @@ NEW FEATURES comment indicator. 3) Add a postproc entry that points to the post that you use. That can be viewed with "mhparam postproc". +- scan(1) -file argument can be a Maildir directory. ----------------- OBSOLETE FEATURES diff --git a/man/scan.man b/man/scan.man index 6d41cebb..7a7396d6 100644 --- a/man/scan.man +++ b/man/scan.man @@ -1,4 +1,4 @@ -.TH SCAN %manext1% 2014-01-20 "%nmhversion%" +.TH SCAN %manext1% 2020-02-17 "%nmhversion%" . .\" %nmhwarning% . @@ -92,13 +92,25 @@ The .I filename switch allows the user to obtain a .B scan -listing of a mail drop file as produced by -.BR packf . +listing of a mail drop. This listing -includes every message in the file (you can't scan individual messages). +includes every message in the mail drop (you can't scan individual messages). The switch .B \-reverse is ignored with this option. +If +.I filename +is a file, it can be in +.B mbox +or +.B MMDF +format, as produced by +.BR packf . +If +.I filename +is a directory, it is considered to be in +.B Maildir +format. .PP The switch .B \-width diff --git a/sbr/maildir_read_and_sort.c b/sbr/maildir_read_and_sort.c new file mode 100644 index 00000000..bb67f47a --- /dev/null +++ b/sbr/maildir_read_and_sort.c @@ -0,0 +1,75 @@ +/* maildir_read_and_sort.c -- returns a sorted list of msgs in a Maildir. + * + * This code is Copyright (c) 2020, by the authors of nmh. See the + * COPYRIGHT file in the root directory of the nmh distribution for + * complete copyright information. + */ + +#include "h/mh.h" + +#include "sbr/maildir_read_and_sort.h" + +#include "sbr/concat.h" +#include "sbr/error.h" + +static int maildir_srt(const void *va, const void *vb) PURE; + +static int +maildir_srt(const void *va, const void *vb) +{ + const struct Maildir_entry *a = va, *b = vb; + if (a->mtime > b->mtime) + return 1; + if (a->mtime < b->mtime) + return -1; + return 0; +} + +static void read_one_dir (char *dirpath, char *dirpath_base, + struct Maildir_entry **maildir, int *maildir_size, + int *nmsgs) { + DIR *md; + struct dirent *de; + struct stat ms; + char *cp; + + cp = concat (dirpath_base, dirpath, NULL); + if ((md = opendir(cp)) == NULL) + die("unable to open %s", cp); + while ((de = readdir (md)) != NULL) { + if (de->d_name[0] == '.') + continue; + if (*nmsgs >= *maildir_size) { + if ((*maildir = realloc(*maildir, sizeof(**maildir) * (2*(*nmsgs)+16))) == NULL) + die("not enough memory for %d messages", 2*(*nmsgs)+16); + *maildir_size = 2*(*nmsgs)+16; + } + (*maildir)[*nmsgs].filename = concat (cp, "/", de->d_name, NULL); + if (stat((*maildir)[*nmsgs].filename, &ms) != 0) + adios ((*maildir)[*nmsgs].filename, "couldn't get delivery time"); + (*maildir)[*nmsgs].mtime = ms.st_mtime; + (*nmsgs)++; + } + free (cp); + closedir (md); +} + +/* + * On return, maildir_out will be NULL or point to memory the caller may free. + */ +void maildir_read_and_sort (char *maildirpath, + struct Maildir_entry **maildir_out, + int *num_maildir_entries_out) { + int num_maildir_entries = 0; + struct Maildir_entry *Maildir = NULL; + int msize = 0; + + read_one_dir("/new", maildirpath, &Maildir, &msize, &num_maildir_entries); + read_one_dir("/cur", maildirpath, &Maildir, &msize, &num_maildir_entries); + if (num_maildir_entries == 0) + die("no mail to incorporate"); + qsort (Maildir, num_maildir_entries, sizeof(*Maildir), maildir_srt); + + *num_maildir_entries_out = num_maildir_entries; + *maildir_out = Maildir; +} diff --git a/sbr/maildir_read_and_sort.h b/sbr/maildir_read_and_sort.h new file mode 100644 index 00000000..a100288f --- /dev/null +++ b/sbr/maildir_read_and_sort.h @@ -0,0 +1,15 @@ +/* maildir_read_and_sort.h -- returns a sorted list of msgs in a Maildir. + * + * This code is Copyright (c) 2020, by the authors of nmh. See the + * COPYRIGHT file in the root directory of the nmh distribution for + * complete copyright information. + */ + +struct Maildir_entry { + char *filename; + time_t mtime; +}; + +void maildir_read_and_sort (char *maildirpath, + struct Maildir_entry **maildir_out, + int *num_msgs_out); diff --git a/test/scan/test-scan-file b/test/scan/test-scan-file new file mode 100755 index 00000000..d2a51b09 --- /dev/null +++ b/test/scan/test-scan-file @@ -0,0 +1,89 @@ +#!/bin/sh +###################################################### +# +# Test "scan -file" +# +###################################################### + +if test -z "${MH_OBJ_DIR}"; then + srcdir=`dirname "$0"`/../.. + MH_OBJ_DIR=`cd "$srcdir" && pwd`; export MH_OBJ_DIR +fi + +. "$MH_OBJ_DIR/test/common.sh" + +expected="$MH_TEST_DIR/$$.expected" +actual="$MH_TEST_DIR/$$.actual" + +setup_test +cat >"$expected" <> + 2 09/29 Test2 Testing message 2<> + 3 09/29 Test3 Testing message 3<> + 4 09/29 Test4 Testing message 4<> + 5 09/29 Test5 Testing message 5<> + 6 09/29 Test6 Testing message 6<> + 7 09/29 Test7 Testing message 7<> + 8 09/29 Test8 Testing message 8<> + 9 09/29 Test9 Testing message 9<> + 10 09/29 Test10 Testing message 10<> +EOF + +## Combine the inbox messages into one spool file + +rm -f "$MH_TEST_DIR/spoolfile" +for i in 1 2 3 4 5 6 7 8 9 10 +do + printf '\1\1\1\1\n' + cat "$MH_TEST_DIR/Mail/inbox/$i" + printf '\1\1\1\1\n' +done > "$MH_TEST_DIR/spoolfile" + +## Test scanning the file + +start_test 'scan file' + +run_prog scan -file "$MH_TEST_DIR/spoolfile" -width 80 >"$actual" || exit 1 +check "$expected" "$actual" 'keep first' + +test -z "$MH_TEST_NOCLEANUP" && rm -f "$MH_TEST_DIR/spoolfile" + +## Convert the test inbox folder into a Maildir directory + +rm -rf "$MH_TEST_DIR/Maildir/new" "$MH_TEST_DIR/Maildir/cur" +mkdir -p "$MH_TEST_DIR/Maildir/new" "$MH_TEST_DIR/Maildir/cur" || exit 1 + +# move the 10 test messages into the Maildir directories +subdir=cur +for i in 1 2 3 4 5 6 7 8 9 10 +do + # split messages between subdirs, to test that "scan" sorts them correctly + if [ "$subdir" = cur ]; then + subdir=new + info= + else + subdir=cur + info=:2, + fi + arith_eval 1567890000 + "$i"; timepart=$arith_val + destfile=$MH_TEST_DIR/Maildir/$subdir/$timepart.P$$.testhost$info + mv "$MH_TEST_DIR/Mail/inbox/$i" "$destfile" + # Maildir sorts by mtime, so make sure all times are unique + touch -c -m --date="@$timepart" "$destfile" +done + +# should be empty now; remove it to make sure "scan -file" is not using it +rmdir "$MH_TEST_DIR/Mail/inbox" + +## Test scanning the Maildir + +start_test 'scan file Maildir' + +run_prog scan -file "$MH_TEST_DIR/Maildir" -width 80 >"$actual" || exit 1 +check "$expected" "$actual" + +test -z "$MH_TEST_NOCLEANUP" && rm -rf "$MH_TEST_DIR/Maildir" + + +finish_test +exit "${failed:-0}" diff --git a/uip/inc.c b/uip/inc.c index 1d8e94f3..7ebb0581 100644 --- a/uip/inc.c +++ b/uip/inc.c @@ -67,6 +67,7 @@ #include "sbr/lock_file.h" #include "sbr/m_maildir.h" #include "sbr/m_mktemp.h" +#include "sbr/maildir_read_and_sort.h" #ifndef TLS_SUPPORT # define TLSminc(a) (a) @@ -118,11 +119,8 @@ DEFINE_SWITCH_ARRAY(INC, switches); #define INC_FILE 0 #define INC_POP 1 -static struct Maildir_entry { - char *filename; - time_t mtime; -} *Maildir = NULL; -static int num_maildir_entries = 0; +static struct Maildir_entry *Maildir; +static int num_maildir_entries; static bool snoop; typedef struct { @@ -181,21 +179,9 @@ static FILE *in; /* * prototypes */ -static int maildir_srt(const void *va, const void *vb) PURE; static void inc_done(int) NORETURN; static int pop_action(void *closure, char *); -static int -maildir_srt(const void *va, const void *vb) -{ - const struct Maildir_entry *a = va, *b = vb; - if (a->mtime > b->mtime) - return 1; - if (a->mtime < b->mtime) - return -1; - return 0; -} - int main (int argc, char **argv) { @@ -478,53 +464,7 @@ main (int argc, char **argv) if (stat (newmail, &s1) == NOTOK || s1.st_size == 0) die("no mail to incorporate"); if (s1.st_mode & S_IFDIR) { - DIR *md; - struct dirent *de; - struct stat ms; - int i; - i = 0; - cp = concat (newmail, "/new", NULL); - if ((md = opendir(cp)) == NULL) - die("unable to open %s", cp); - while ((de = readdir (md)) != NULL) { - if (de->d_name[0] == '.') - continue; - if (i >= num_maildir_entries) { - if ((Maildir = realloc(Maildir, sizeof(*Maildir) * (2*i+16))) == NULL) - die("not enough memory for %d messages", 2*i+16); - num_maildir_entries = 2*i+16; - } - Maildir[i].filename = concat (cp, "/", de->d_name, NULL); - if (stat(Maildir[i].filename, &ms) != 0) - adios (Maildir[i].filename, "couldn't get delivery time"); - Maildir[i].mtime = ms.st_mtime; - i++; - } - free (cp); - closedir (md); - cp = concat (newmail, "/cur", NULL); - if ((md = opendir(cp)) == NULL) - die("unable to open %s", cp); - while ((de = readdir (md)) != NULL) { - if (de->d_name[0] == '.') - continue; - if (i >= num_maildir_entries) { - if ((Maildir = realloc(Maildir, sizeof(*Maildir) * (2*i+16))) == NULL) - die("not enough memory for %d messages", 2*i+16); - num_maildir_entries = 2*i+16; - } - Maildir[i].filename = concat (cp, "/", de->d_name, NULL); - if (stat(Maildir[i].filename, &ms) != 0) - adios (Maildir[i].filename, "couldn't get delivery time"); - Maildir[i].mtime = ms.st_mtime; - i++; - } - free (cp); - closedir (md); - if (i == 0) - die("no mail to incorporate"); - num_maildir_entries = i; - qsort (Maildir, num_maildir_entries, sizeof(*Maildir), maildir_srt); + maildir_read_and_sort(newmail, &Maildir, &num_maildir_entries); } cp = mh_xstrdup(newmail); diff --git a/uip/scan.c b/uip/scan.c index 12879050..c1f27194 100644 --- a/uip/scan.c +++ b/uip/scan.c @@ -35,6 +35,7 @@ #include "h/utils.h" #include "sbr/m_maildir.h" #include "sbr/terminal.h" +#include "sbr/maildir_read_and_sort.h" #define SCAN_SWITCHES \ X("clear", 0, CLRSW) \ @@ -64,7 +65,7 @@ main (int argc, char **argv) { bool clearflag = false; bool hdrflag = false; - int ontty; + int ontty = 0; int width = -1; bool revflag = false; int i, state, msgnum; @@ -91,13 +92,13 @@ main (int argc, char **argv) while ((cp = *argp++)) { if (*cp == '-') { switch (smatch (++cp, switches)) { - case AMBIGSW: + case AMBIGSW: ambigsw (cp, switches); done (1); - case UNKWNSW: + case UNKWNSW: die("-%s unknown", cp); - case HELPSW: + case HELPSW: snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name); print_help (buf, switches, 1); @@ -106,32 +107,32 @@ main (int argc, char **argv) print_version(invo_name); done (0); - case CLRSW: + case CLRSW: clearflag = true; continue; - case NCLRSW: + case NCLRSW: clearflag = false; continue; - case FORMSW: + case FORMSW: if (!(form = *argp++) || *form == '-') die("missing argument to %s", argp[-2]); format = NULL; continue; - case FMTSW: + case FMTSW: if (!(format = *argp++) || *format == '-') die("missing argument to %s", argp[-2]); form = NULL; continue; - case HEADSW: + case HEADSW: hdrflag = true; continue; - case NHEADSW: + case NHEADSW: hdrflag = false; continue; - case WIDTHSW: + case WIDTHSW: if (!(cp = *argp++) || *cp == '-') die("missing argument to %s", argp[-2]); width = atoi (cp); @@ -167,10 +168,10 @@ main (int argc, char **argv) */ nfs = new_fs (form, format, FORMAT); - /* - * We are scanning a maildrop file - */ if (file) { + /* + * We have a -file argument + */ if (msgs.size) die("\"msgs\" not allowed with -file"); if (folder) @@ -181,10 +182,63 @@ main (int argc, char **argv) in = stdin; file = "stdin"; } else { - if ((in = fopen (file, "r")) == NULL) - adios (file, "unable to open"); + /* check if "file" is really a Maildir folder */ + struct stat st; + if (stat (file, &st) == NOTOK) + adios (file, "unable to stat"); + if (!(st.st_mode & S_IFDIR)) { + if ((in = fopen (file, "r")) == NULL) + adios (file, "unable to open"); + } else { + /* + * We are scanning a Maildir folder + */ + struct Maildir_entry *Maildir; + int num_maildir_entries; + int msgnum = 0; + char *fnp; + FILE *in; + int i; + + maildir_read_and_sort(file, &Maildir, &num_maildir_entries); + for (i = 0; i < num_maildir_entries; i++) { + msgnum++; + fnp = Maildir[i].filename; + + if ((in = fopen (fnp, "r")) == NULL) { + admonish (fnp, "unable to open message"); + continue; + } + + switch (state = scan (in, msgnum, 0, nfs, width, + 0, 0, hdrflag ? file : NULL, + 0L, 1, &scanl)) { + case SCNMSG: + case SCNERR: + break; + + default: + die("scan() botch (%d)", state); + + case SCNEOF: + inform("message %d: empty", msgnum); + break; + } + if (scanl) + charstring_clear(scanl); + scan_finished (); + hdrflag = false; + fclose (in); + if (ontty) + fflush (stdout); + } + done (0); + } } + /* + * We are scanning a maildrop file + */ if (hdrflag) { printf ("FOLDER %s\t%s\n", file, dtimenow (1)); } @@ -282,14 +336,14 @@ main (int argc, char **argv) switch (state = scan (in, msgnum, 0, nfs, width, msgnum == mp->curmsg, unseen, folder, 0L, 1, &scanl)) { - case SCNMSG: - case SCNERR: + case SCNMSG: + case SCNERR: break; - default: + default: die("scan() botch (%d)", state); - case SCNEOF: + case SCNEOF: inform("message %d: empty", msgnum); break; }