]> diplodocus.org Git - nmh/commitdiff
patch: Maildir support for "scan -file"
authorStephen Gildea <stepheng+nmh@gildea.com>
Mon, 17 Feb 2020 19:02:49 +0000 (11:02 -0800)
committerDavid Levine <levinedl@acm.org>
Sat, 22 Feb 2020 17:09:52 +0000 (12:09 -0500)
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 <levinedl@acm.org>
Makefile.am
docs/pending-release-notes
man/scan.man
sbr/maildir_read_and_sort.c [new file with mode: 0644]
sbr/maildir_read_and_sort.h [new file with mode: 0644]
test/scan/test-scan-file [new file with mode: 0755]
uip/inc.c
uip/scan.c

index ffc4d65b027796a231db4c5aa9d8741e04e8b17e..329a762a99421f455195b03bdacf6c96e9e41276 100644 (file)
@@ -150,6 +150,7 @@ TESTS = \
     test/repl/test-trailing-newline \
     test/scan/test-header-parsing \
     test/scan/test-scan \
     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 \
     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/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 \
     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/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 \
     sbr/makedir.c \
     sbr/message_id.c \
     sbr/mf.c \
index 5a932160d96f3068ae32bde3480cf71b7c2a9907..a2c415c9f2bcde946ee4f260954c9092f84ccd6b 100644 (file)
@@ -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".
      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
 
 -----------------
 OBSOLETE FEATURES
index 6d41cebb1874376f1f62c53749af2746a52f3e5f..7a7396d691367acd98360cc11691d3d9d20b01b4 100644 (file)
@@ -1,4 +1,4 @@
-.TH SCAN %manext1% 2014-01-20 "%nmhversion%"
+.TH SCAN %manext1% 2020-02-17 "%nmhversion%"
 .
 .\" %nmhwarning%
 .
 .
 .\" %nmhwarning%
 .
@@ -92,13 +92,25 @@ The
 .I filename
 switch allows the user to obtain a
 .B scan
 .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
 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.
 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
 .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 (file)
index 0000000..bb67f47
--- /dev/null
@@ -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 (file)
index 0000000..a100288
--- /dev/null
@@ -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 (executable)
index 0000000..d2a51b0
--- /dev/null
@@ -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" <<EOF
+   1  09/29 Test1              Testing message 1<<This is message number 1 >>
+   2  09/29 Test2              Testing message 2<<This is message number 2 >>
+   3  09/29 Test3              Testing message 3<<This is message number 3 >>
+   4  09/29 Test4              Testing message 4<<This is message number 4 >>
+   5  09/29 Test5              Testing message 5<<This is message number 5 >>
+   6  09/29 Test6              Testing message 6<<This is message number 6 >>
+   7  09/29 Test7              Testing message 7<<This is message number 7 >>
+   8  09/29 Test8              Testing message 8<<This is message number 8 >>
+   9  09/29 Test9              Testing message 9<<This is message number 9 >>
+  10  09/29 Test10             Testing message 10<<This is message number 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}"
index 1d8e94f35aae5e86dd4d332816a171efdd458504..7ebb0581c1f206befb1764d3794af17f8313b395 100644 (file)
--- 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/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)
 
 #ifndef TLS_SUPPORT
 # define TLSminc(a) (a)
@@ -118,11 +119,8 @@ DEFINE_SWITCH_ARRAY(INC, switches);
 #define INC_FILE  0
 #define INC_POP   1
 
 #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 {
 static bool snoop;
 
 typedef struct {
@@ -181,21 +179,9 @@ static FILE *in;
 /*
  * prototypes
  */
 /*
  * 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 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)
 {
 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) {
        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);
        }
 
        cp = mh_xstrdup(newmail);
index 12879050c0afcb77504a513602032a417e09f62a..c1f27194de1420f3c61f9a5af914bd7033116c34 100644 (file)
@@ -35,6 +35,7 @@
 #include "h/utils.h"
 #include "sbr/m_maildir.h"
 #include "sbr/terminal.h"
 #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) \
 
 #define SCAN_SWITCHES \
     X("clear", 0, CLRSW) \
@@ -64,7 +65,7 @@ main (int argc, char **argv)
 {
     bool clearflag = false;
     bool hdrflag = false;
 {
     bool clearflag = false;
     bool hdrflag = false;
-    int ontty;
+    int ontty = 0;
     int width = -1;
     bool revflag = false;
     int i, state, msgnum;
     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)) {
     while ((cp = *argp++)) {
        if (*cp == '-') {
            switch (smatch (++cp, switches)) {
-               case AMBIGSW: 
+               case AMBIGSW:
                    ambigsw (cp, switches);
                    done (1);
                    ambigsw (cp, switches);
                    done (1);
-               case UNKWNSW: 
+               case UNKWNSW:
                    die("-%s unknown", cp);
 
                    die("-%s unknown", cp);
 
-               case HELPSW: 
+               case HELPSW:
                    snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
                        invo_name);
                    print_help (buf, switches, 1);
                    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);
 
                    print_version(invo_name);
                    done (0);
 
-               case CLRSW: 
+               case CLRSW:
                    clearflag = true;
                    continue;
                    clearflag = true;
                    continue;
-               case NCLRSW: 
+               case NCLRSW:
                    clearflag = false;
                    continue;
 
                    clearflag = false;
                    continue;
 
-               case FORMSW: 
+               case FORMSW:
                    if (!(form = *argp++) || *form == '-')
                        die("missing argument to %s", argp[-2]);
                    format = NULL;
                    continue;
                    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;
 
                    if (!(format = *argp++) || *format == '-')
                        die("missing argument to %s", argp[-2]);
                    form = NULL;
                    continue;
 
-               case HEADSW: 
+               case HEADSW:
                    hdrflag = true;
                    continue;
                    hdrflag = true;
                    continue;
-               case NHEADSW: 
+               case NHEADSW:
                    hdrflag = false;
                    continue;
 
                    hdrflag = false;
                    continue;
 
-               case WIDTHSW: 
+               case WIDTHSW:
                    if (!(cp = *argp++) || *cp == '-')
                        die("missing argument to %s", argp[-2]);
                    width = atoi (cp);
                    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);
 
      */
     nfs = new_fs (form, format, FORMAT);
 
-    /*
-     * We are scanning a maildrop file
-     */
     if (file) {
     if (file) {
+        /*
+         * We have a -file argument
+         */
        if (msgs.size)
            die("\"msgs\" not allowed with -file");
        if (folder)
        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 {
            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));
        }
        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)) {
            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;
 
                    break;
 
-               default: 
+               default:
                    die("scan() botch (%d)", state);
 
                    die("scan() botch (%d)", state);
 
-               case SCNEOF: 
+               case SCNEOF:
                    inform("message %d: empty", msgnum);
                    break;
            }
                    inform("message %d: empty", msgnum);
                    break;
            }