nmh-workers
[Top] [All Lists]

Re: [Nmh-workers] New new/fn/fp/unseen program

2008-08-12 15:11:59
Eric Gillespie <epg(_at_)pretzelnet(_dot_)org> writes:

I'll be back in a week or two (I'm on leave with a new baby :)
with the following changes:

- handle multiple unseen sequences (and other sequences)
- fix the one hard-coded "unseen"
- configurable .folders in .mh_profile (new: -f my.folders)

All done.  I've been waiting for comment on the crawl_folders
patch, but maybe there just isn't yet sufficient motive to
consider such a patch.  So, here's the latest, including new.c
and update crawl_folders.

I think that covers it.  Things I'm not doing (yet):

- changing the command names (what should they be called?)

I'm fine with this, but unsure of new names.  Are fnext/fprev
really better?  'new' may be a worse offender than 'fn' or 'fp'
anyway.  I threw out prefixing them all with 'mh' (mhnew), but
saw no comment on that.

- not using system(3) in unseen, or dropping unseen altogether

I have a stack of other things I want to do first, but afterwards
I may take a look at this.  I never use the unseen command, so
just don't care about this one much.  I'd be fine with dropping
it until someone refactors scan.

- man page (I'll write it if this goes in, and it will document
  folders -fast -recur > `mhpath +`/.folders)

I'm willing to do this if the new commands are accepted.


=== modified file 'ChangeLog'
--- ChangeLog   2008-08-13 01:04:29 +0000
+++ ChangeLog   2008-08-12 21:54:57 +0000
@@ -1,3 +1,21 @@
+2008-08-12  Eric Gillespie  <epg(_at_)pretzelnet(_dot_)org>
+
+       * uip/Makefile.in, uip/new.c, test/tests/new/test-basic: Add new
+       program, and fn/fp/unseen symlinks.
+
+       * test/{runtest,setup-test}: Move MH profile under Mail directory
+       so each test script will have its own to muck with, if needed.
+
+2008-08-12  Eric Gillespie  <epg(_at_)pretzelnet(_dot_)org>
+
+       * h/Makefile.in, h/crawl_folders.h, sbr/Makefile.in,
+       sbr/crawl_folders.c, uip/folder.c: Extract the folder crawling
+       code from folder.c into new crawl_folders function, using a
+       callback to assemble the folder info in folder.c.  Drop compare
+       function and use strcmp instead.  Rename addfold and addir to
+       add_folder and add_children (add dir vs. add folder?
+       confusing names).
+
 2008-08-12  Peter Maydell  
<pmaydell(_at_)chiark(_dot_)greenend(_dot_)org(_dot_)uk>
 
        * autogen.sh (new file): add script for running the GNU

=== modified file 'h/Makefile.in'
--- h/Makefile.in       2007-01-16 02:08:10 +0000
+++ h/Makefile.in       2008-08-06 23:29:50 +0000
@@ -10,7 +10,7 @@
 VPATH  = @srcdir@
 
 # header files included in distribution
-HDRS =         addrsbr.h aliasbr.h dropsbr.h fmt_compile.h fmt_scan.h          
\
+HDRS =         addrsbr.h aliasbr.h crawl_folders.h dropsbr.h fmt_compile.h 
fmt_scan.h \
        md5.h mf.h mh.h mhcachesbr.h mhparse.h mime.h msh.h mts.h       \
        netdb.h nmh.h nntp.h picksbr.h popsbr.h prototypes.h rcvmail.h  \
        scansbr.h signals.h tws.h vmhsbr.h utils.h

=== added file 'h/crawl_folders.h'
--- h/crawl_folders.h   1970-01-01 00:00:00 +0000
+++ h/crawl_folders.h   2008-08-08 06:08:10 +0000
@@ -0,0 +1,18 @@
+
+/*
+ * crawl_folders.h -- crawl folder hierarchy
+ *
+ * $Id$
+ */
+
+#define CRAWL_NUMFOLDERS 100
+
+/* Callbacks return TRUE crawl_folders should crawl the children of `folder'.
+ * Callbacks need not duplicate folder, as crawl_folders does not free it. */
+typedef boolean (crawl_callback_t)(char *folder, void *baton);
+
+/* Crawl the folder hierarchy rooted at the relative path `dir'.  For each
+ * folder, pass `callback' the folder name (as a path relative to the current
+ * directory) and `baton'; the callback may direct crawl_folders not to crawl
+ * its children; see above. */
+void crawl_folders (char *dir, crawl_callback_t *callback, void *baton);

=== modified file 'sbr/Makefile.in'
--- sbr/Makefile.in     2008-05-20 19:04:57 +0000
+++ sbr/Makefile.in     2008-08-06 23:30:15 +0000
@@ -58,7 +58,8 @@
        check_charset.c client.c closefds.c concat.c context_del.c      \
        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  \
+       copyip.c cpydata.c cpydgst.c crawl_folders.c                    \
+       discard.c done.c dtime.c dtimep.c                               \
        error.c ext_hook.c 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                    \

=== added file 'sbr/crawl_folders.c'
--- sbr/crawl_folders.c 1970-01-01 00:00:00 +0000
+++ sbr/crawl_folders.c 2008-08-06 23:41:08 +0000
@@ -0,0 +1,151 @@
+
+/*
+ * crawl_folders.c -- crawl folder hierarchy
+ *
+ * $Id$
+ *
+ * This code is Copyright (c) 2008, 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 <h/crawl_folders.h>
+#include <h/utils.h>
+
+struct crawl_context {
+    int max;                   /* how many folders we currently can hold in
+                                * the array `folders', increased by
+                                * CRAWL_NUMFOLDERS at a time */
+    int total;                 /* how many `folders' actually has */
+    char **folders;            /* the array of folders */
+    int start;
+    int foldp;
+};
+
+/*
+ * Add the folder name into the
+ * list in a sorted fashion.
+ */
+
+static void
+add_folder (char *fold, struct crawl_context *crawl)
+{
+    register int i, j;
+
+    /* if necessary, reallocate the space for folder names */
+    if (crawl->foldp >= crawl->max) {
+       crawl->max += CRAWL_NUMFOLDERS;
+       crawl->folders = mh_xrealloc (crawl->folders,
+                                     crawl->max * sizeof(char *));
+    }
+
+    for (i = crawl->start; i < crawl->foldp; i++)
+       if (strcmp (fold, crawl->folders[i]) < 0) {
+           for (j = crawl->foldp - 1; j >= i; j--)
+               crawl->folders[j + 1] = crawl->folders[j];
+           crawl->foldp++;
+           crawl->folders[i] = fold;
+           return;
+       }
+
+    crawl->total++;
+    crawl->folders[crawl->foldp++] = fold;
+}
+
+static void
+add_children (char *name, struct crawl_context *crawl)
+{
+    char *prefix, *child;
+    struct stat st;
+    struct dirent *dp;
+    DIR * dd;
+    int child_is_folder;
+
+    if (!(dd = opendir (name))) {
+       admonish (name, "unable to read directory ");
+       return;
+    }
+
+    if (strcmp (name, ".") == 0) {
+       prefix = getcpy ("");
+    } else {
+       prefix = concat (name, "/", (void *)NULL);
+    }
+
+    while ((dp = readdir (dd))) {
+       /* If the system supports it, try to skip processing of children we
+        * know are not directories or symlinks. */
+       child_is_folder = -1;
+#if defined(HAVE_STRUCT_DIRENT_D_TYPE)
+       if (dp->d_type == DT_DIR) {
+           child_is_folder = 1;
+       } else if (dp->d_type != DT_LNK && dp->d_type != DT_UNKNOWN) {
+           continue;
+       }
+#endif
+       if (!strcmp (dp->d_name, ".") || !strcmp (dp->d_name, "..")) {
+           continue;
+       }
+       child = concat (prefix, dp->d_name, (void *)NULL);
+       /* If we have no d_type or d_type is DT_LNK or DT_UNKNOWN, stat the
+        * child to see what it is. */
+       if (child_is_folder == -1) {
+           child_is_folder = (stat (child, &st) != -1 && S_ISDIR(st.st_mode));
+       }
+       if (child_is_folder) {
+           /* add_folder saves child in the list, don't free it */
+           add_folder (child, crawl);
+       } else {
+           free (child);
+       }
+    }
+
+    closedir (dd);
+    free(prefix);
+}
+
+static void
+crawl_folders_body (struct crawl_context *crawl,
+                   char *dir, crawl_callback_t *callback, void *baton)
+{
+    int i;
+    int os = crawl->start;
+    int of = crawl->foldp;
+
+    crawl->start = crawl->foldp;
+
+    add_children (dir, crawl);
+
+    for (i = crawl->start; i < crawl->foldp; i++) {
+       char *fold = crawl->folders[i];
+       int crawl_children = 1;
+
+       if (callback != NULL) {
+           crawl_children = callback (fold, baton);
+       }
+
+       if (crawl_children) {
+           crawl_folders_body (crawl, fold, callback, baton);
+       }
+    }
+
+    crawl->start = os;
+    crawl->foldp = of;
+}
+
+void
+crawl_folders (char *dir, crawl_callback_t *callback, void *baton)
+{
+    struct crawl_context *crawl = mh_xmalloc (sizeof(*crawl));
+    crawl->max = CRAWL_NUMFOLDERS;
+    crawl->total = crawl->start = crawl->foldp = 0;
+    crawl->folders = mh_xmalloc (crawl->max * sizeof(*crawl->folders));
+
+    crawl_folders_body (crawl, dir, callback, baton);
+
+    /* Note that we "leak" the folder names, on the assumption that the caller
+     * is using them. */
+    free (crawl->folders);
+    free (crawl);
+}

=== modified file 'test/runtest'
--- test/runtest        2008-08-03 22:13:34 +0000
+++ test/runtest        2008-08-08 00:39:34 +0000
@@ -1,13 +1,14 @@
 #!/bin/sh
 
 export MH_TEST_DIR=`cat test-temp-dir`
-export MH=$MH_TEST_DIR/mh_profile
+export MH=$MH_TEST_DIR/Mail/.mh_profile
 export PATH=$MH_TEST_DIR/bin:$PATH
 
 # clean old test data
 rm -rf $MH_TEST_DIR/Mail
 # setup test data
 mkdir $MH_TEST_DIR/Mail
+echo "Path: $MH_TEST_DIR/Mail" > $MH
 folder -create +inbox > /dev/null
 # create 10 basic messages
 for i in `seq 1 10`;

=== modified file 'test/setup-test'
--- test/setup-test     2008-08-13 01:04:29 +0000
+++ test/setup-test     2008-08-12 21:50:48 +0000
@@ -10,5 +10,3 @@
 ./configure --prefix=$TEMPDIR --with-locking=fcntl --enable-debug
 make clean
 make install
-
-echo "Path: $TEMPDIR/Mail" > $TEMPDIR/mh_profile

=== added directory 'test/tests/new'
=== added file 'test/tests/new/test-basic'
--- test/tests/new/test-basic   1970-01-01 00:00:00 +0000
+++ test/tests/new/test-basic   2008-08-08 03:01:14 +0000
@@ -0,0 +1,151 @@
+#!/bin/sh
+
+# TODO: Move to a common file tests can source; need more framework...
+failed=0
+check() {
+    diff -u $expected $actual
+    if [ $? -ne 0 ]; then
+        failed=$((failed + 1))
+    fi
+}
+
+folders=$MH_TEST_DIR/Mail/.folders
+
+expected=$MH_TEST_DIR/$$.expected
+actual=$MH_TEST_DIR/$$.actual
+
+# make second folder
+cp -r $MH_TEST_DIR/Mail/inbox $MH_TEST_DIR/Mail/foo1
+cp -r $MH_TEST_DIR/Mail/inbox $MH_TEST_DIR/Mail/foo2
+# but only list inbox and foo2 in .folders, and sorted differently
+cat > $folders <<EOF
+inbox
+foo2
+EOF
+
+# test with no sequence
+cat > $expected <<EOF
+new: must specify sequences or set Unseen-Sequence
+EOF
+new > $actual 2>&1
+diff -u $expected $actual
+
+# test with no desired messages
+cat > $expected <<EOF
+ total      0.
+EOF
+new aseq > $actual 2>&1
+check
+new -f $folders aseq > $actual 2>&1
+check
+
+# add 1 desired message in each folder
+echo 'aseq: 1' > $MH_TEST_DIR/Mail/inbox/.mh_sequences
+echo 'aseq: 1' > $MH_TEST_DIR/Mail/foo1/.mh_sequences
+echo 'aseq: 1' > $MH_TEST_DIR/Mail/foo2/.mh_sequences
+
+# test with all folders
+cat > $expected <<EOF
+foo1       1.  1
+foo2       1.  1
+inbox      1.* 1
+ total      3.
+EOF
+new aseq > $actual 2>&1
+check
+
+# test with .folders
+cat > $expected <<EOF
+inbox      1.* 1
+foo2       1.  1
+ total      2.
+EOF
+new -f $folders aseq > $actual 2>&1
+check
+
+# add 2 desired messages to another sequence in each folder
+echo 'bseq: 3-4' >> $MH_TEST_DIR/Mail/inbox/.mh_sequences
+echo 'bseq: 3-4' >> $MH_TEST_DIR/Mail/foo1/.mh_sequences
+echo 'bseq: 3-4' >> $MH_TEST_DIR/Mail/foo2/.mh_sequences
+
+# test listing aseq and bseq
+cat > $expected <<EOF
+foo1       3.  1 3-4
+foo2       3.  1 3-4
+inbox      3.* 1 3-4
+ total      9.
+EOF
+new aseq bseq > $actual 2>&1
+check
+
+# set aseq bseq as unseen
+echo 'Unseen-Sequence: aseq bseq' >> $MH
+new > $actual 2>&1
+check
+
+# test unseen
+cat > $expected <<EOF
+
+3 aseq bseq messages in foo1
+   1  09/29 Test1              Testing message 1<<This is message number 1 >>
+   3  09/29 Test3              Testing message 3<<This is message number 3 >>
+   4  09/29 Test4              Testing message 4<<This is message number 4 >>
+
+3 aseq bseq messages in foo2
+   1  09/29 Test1              Testing message 1<<This is message number 1 >>
+   3  09/29 Test3              Testing message 3<<This is message number 3 >>
+   4  09/29 Test4              Testing message 4<<This is message number 4 >>
+
+3 aseq bseq messages in inbox (*: current folder)
+   1  09/29 Test1              Testing message 1<<This is message number 1 >>
+   3  09/29 Test3              Testing message 3<<This is message number 3 >>
+   4  09/29 Test4              Testing message 4<<This is message number 4 >>
+EOF
+unseen > $actual 2>&1
+check
+
+# test fp with the current folder not in the list
+echo 'Current-Folder: foo1' > $MH_TEST_DIR/Mail/context
+echo 'inbox  1 3-4' > $expected
+fn -f $folders > $actual 2>&1
+check
+
+# test fn with current folder in the middle of the list
+echo 'Current-Folder: foo2' > $MH_TEST_DIR/Mail/context
+echo 'inbox  1 3-4' > $expected
+fn > $actual 2>&1
+check
+
+# test fp with current folder in the middle of the list
+echo 'Current-Folder: foo2' > $MH_TEST_DIR/Mail/context
+echo 'foo1  1 3-4' > $expected
+fp > $actual 2>&1
+check
+
+# test fp with current folder at the beginning of the list
+echo 'Current-Folder: foo1' > $MH_TEST_DIR/Mail/context
+echo 'inbox  1 3-4' > $expected
+fp > $actual 2>&1
+check
+
+# test fn with current folder at the end of the list
+echo 'Current-Folder: inbox' > $MH_TEST_DIR/Mail/context
+echo 'foo1  1 3-4' > $expected
+fn > $actual 2>&1
+check
+
+# test fn with no current folder
+rm $MH_TEST_DIR/Mail/context
+echo 'foo1  1 3-4' > $expected
+fn > $actual 2>&1
+check
+
+# test fn with only one folder in the list
+cat > $folders <<EOF
+inbox
+EOF
+echo 'inbox  1 3-4' > $expected
+fn -f $folders > $actual 2>&1
+check
+
+exit $failed

=== modified file 'uip/Makefile.in'
--- uip/Makefile.in     2005-12-24 17:17:38 +0000
+++ uip/Makefile.in     2008-07-28 07:10:25 +0000
@@ -60,7 +60,7 @@
 # commands to build
 CMDS = ali anno burst comp dist flist folder forw install-mh mark mhbuild \
        mhlist mhmail mhn mhparam mhpath mhshow mhstore msgchk \
-       msh packf pick prompter refile repl rmf rmm scan send show \
+       msh new packf pick prompter refile repl rmf rmm scan send show \
        sortm whatnow whom
 
 ## removed this from CMDS until I can fix it
@@ -83,7 +83,7 @@
        mhbuildsbr.c mhcachesbr.c mhfree.c mhl.c mhlist.c mhlistsbr.c     \
        mhlsbr.c mhmail.c mhmisc.c mhn.c mhoutsbr.c mhparam.c mhparse.c   \
        mhpath.c mhshow.c mhshowsbr.c mhstore.c mhstoresbr.c mhtest.c     \
-       msgchk.c msh.c mshcmds.c packf.c pick.c picksbr.c popi.c popsbr.c \
+       msgchk.c msh.c mshcmds.c new.c packf.c pick.c picksbr.c popi.c popsbr.c 
\
        post.c prompter.c rcvdist.c rcvpack.c rcvstore.c rcvtty.c         \
        refile.c repl.c replsbr.c rmf.c rmm.c scan.c scansbr.c send.c     \
        sendsbr.c show.c slocal.c sortm.c spost.c termsbr.c viamail.c     \
@@ -183,6 +183,9 @@
 msh: msh.o mshcmds.o vmhsbr.o picksbr.o scansbr.o dropsbr.o mhlsbr.o termsbr.o 
$(LOCALLIBS)
        $(LINK) msh.o mshcmds.o vmhsbr.o picksbr.o scansbr.o dropsbr.o mhlsbr.o 
termsbr.o $(LINKLIBS) $(TERMLIB)
 
+new: new.o $(LOCALLIBS)
+       $(LINK) new.o $(LINKLIBS)
+
 packf: packf.o dropsbr.o $(LOCALLIBS)
        $(LINK) packf.o dropsbr.o $(LINKLIBS)
 
@@ -265,11 +268,17 @@
 install-lcmds:
        rm -f $(DESTDIR)$(bindir)/flists
        rm -f $(DESTDIR)$(bindir)/folders
+       rm -f $(DESTDIR)$(bindir)/fn
+       rm -f $(DESTDIR)$(bindir)/fp
+       rm -f $(DESTDIR)$(bindir)/unseen
        rm -f $(DESTDIR)$(bindir)/prev
        rm -f $(DESTDIR)$(bindir)/next
        rm -f $(DESTDIR)$(libdir)/install-mh
        $(LN) $(DESTDIR)$(bindir)/flist  $(DESTDIR)$(bindir)/flists
        $(LN) $(DESTDIR)$(bindir)/folder $(DESTDIR)$(bindir)/folders
+       $(LN) $(DESTDIR)$(bindir)/new    $(DESTDIR)$(bindir)/fn
+       $(LN) $(DESTDIR)$(bindir)/new    $(DESTDIR)$(bindir)/fp
+       $(LN) $(DESTDIR)$(bindir)/new    $(DESTDIR)$(bindir)/unseen
        $(LN) $(DESTDIR)$(bindir)/show   $(DESTDIR)$(bindir)/prev
        $(LN) $(DESTDIR)$(bindir)/show   $(DESTDIR)$(bindir)/next
 

=== modified file 'uip/folder.c'
--- uip/folder.c        2008-08-06 04:06:00 +0000
+++ uip/folder.c        2008-08-12 21:58:28 +0000
@@ -6,12 +6,13 @@
  *
  * $Id: folder.c,v 1.16 2008/08/05 21:06:00 pm215 Exp $
  *
- * This code is Copyright (c) 2002, by the authors of nmh.  See the
+ * This code is Copyright (c) 2002, 2008, 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 <h/crawl_folders.h>
 #include <h/utils.h>
 #include <errno.h>
 
@@ -78,22 +79,10 @@
 
 static int total_folders = 0;  /* total number of folders                  */
 
-static int start = 0;
-static int foldp = 0;
-
 static char *nmhdir;
 static char *stack = "Folder-Stack";
 static char folder[BUFSIZ];
 
-#define NUMFOLDERS 100
-
-/*
- * This is how many folders we currently can hold in the array
- * `folds'.  We increase this amount by NUMFOLDERS at a time.
- */
-static int maxfolders;
-static char **folds;
-
 /*
  * Structure to hold information about
  * folders as we scan them.
@@ -118,13 +107,10 @@
 /*
  * static prototypes
  */
-static void dodir (char *);
 static int get_folder_info (char *, char *);
+static crawl_callback_t get_folder_info_callback;
 static void print_folders (void);
 static int sfold (struct msgs *, char *);
-static void addir (char *);
-static void addfold (char *);
-static int compare (char *, char *);
 static void readonly_folders (void);
 
 
@@ -350,12 +336,8 @@
            done (0);
     }
 
-    /* Allocate initial space to record folder names */
-    maxfolders = NUMFOLDERS;
-    folds = mh_xmalloc (maxfolders * sizeof(char *));
-
     /* Allocate initial space to record folder information */
-    maxFolderInfo = NUMFOLDERS;
+    maxFolderInfo = CRAWL_NUMFOLDERS;
     fi = mh_xmalloc (maxFolderInfo * sizeof(*fi));
 
     /*
@@ -365,7 +347,7 @@
        /*
         * If no folder is given, do them all
         */
-       /* change directory to base of nmh directory for dodir */
+       /* change directory to base of nmh directory for crawl_folders */
        if (chdir (nmhdir) == NOTOK)
            adios (nmhdir, "unable to change directory to");
        if (!argfolder) {
@@ -373,7 +355,7 @@
                admonish (NULL, "no folder given for message %s", msg);
            readonly_folders (); /* do any readonly folders */
            strncpy (folder, (cp = context_find (pfolder)) ? cp : "", 
sizeof(folder));
-           dodir (".");
+           crawl_folders (".", get_folder_info_callback, NULL);
        } else {
            strncpy (folder, argfolder, sizeof(folder));
            if (get_folder_info (argfolder, msg)) {
@@ -385,7 +367,7 @@
             * we still need to list all level-1 sub-folders.
             */
            if (!frecurse)
-               dodir (folder);
+               crawl_folders (folder, get_folder_info_callback, NULL);
        }
     } else {
        strncpy (folder, argfolder ? argfolder : getfolder (1), sizeof(folder));
@@ -412,32 +394,8 @@
     return 1;
 }
 
-/*
- * Base routine for scanning a folder
- */
-
-static void
-dodir (char *dir)
-{
-    int i;
-    int os = start;
-    int of = foldp;
-
-    start = foldp;
-
-    addir (dir);
-
-    for (i = start; i < foldp; i++) {
-       get_folder_info (folds[i], NULL);
-       fflush (stdout);
-    }
-
-    start = os;
-    foldp = of;
-}
-
 static int
-get_folder_info (char *fold, char *msg)
+get_folder_info_body (char *fold, char *msg, boolean *crawl_children)
 {
     int        i, retval = 1;
     struct msgs *mp = NULL;
@@ -449,7 +407,7 @@
      * for folder information
      */
     if (total_folders >= maxFolderInfo) {
-       maxFolderInfo += NUMFOLDERS;
+       maxFolderInfo += CRAWL_NUMFOLDERS;
        fi = mh_xrealloc (fi, maxFolderInfo * sizeof(*fi));
     }
 
@@ -493,8 +451,32 @@
        folder_free (mp); /* free folder/message structure */
     }
 
-    if (frecurse && (fshort || fi[i].others) && (fi[i].error == 0))
-       dodir (fold);
+    *crawl_children = (frecurse && (fshort || fi[i].others)
+                      && (fi[i].error == 0));
+    return retval;
+}
+
+static boolean
+get_folder_info_callback (char *fold, void *baton)
+{
+    boolean crawl_children;
+    get_folder_info_body (fold, NULL, &crawl_children);
+    fflush (stdout);
+    return crawl_children;
+}
+
+static int
+get_folder_info (char *fold, char *msg)
+{
+    boolean crawl_children;
+    int retval;
+
+    retval = get_folder_info_body (fold, msg, &crawl_children);
+
+    if (crawl_children) {
+       crawl_folders (fold, get_folder_info_callback, NULL);
+    }
+
     return retval;
 }
 
@@ -654,99 +636,6 @@
 }
 
 
-static void
-addir (char *name)
-{
-    char *prefix, *child;
-    struct stat st;
-    struct dirent *dp;
-    DIR * dd;
-    int child_is_folder;
-
-    if (!(dd = opendir (name))) {
-       admonish (name, "unable to read directory ");
-       return;
-    }
-
-    if (strcmp (name, ".") == 0) {
-       prefix = getcpy ("");
-    } else {
-       prefix = concat (name, "/", (void *)NULL);
-    }
-
-    while ((dp = readdir (dd))) {
-       /* If the system supports it, try to skip processing of children we
-        * know are not directories or symlinks. */
-       child_is_folder = -1;
-#if defined(HAVE_STRUCT_DIRENT_D_TYPE)
-       if (dp->d_type == DT_DIR) {
-           child_is_folder = 1;
-       } else if (dp->d_type != DT_LNK && dp->d_type != DT_UNKNOWN) {
-           continue;
-       }
-#endif
-       if (!strcmp (dp->d_name, ".") || !strcmp (dp->d_name, "..")) {
-           continue;
-       }
-       child = concat (prefix, dp->d_name, (void *)NULL);
-       /* If we have no d_type or d_type is DT_LNK or DT_UNKNOWN, stat the
-        * child to see what it is. */
-       if (child_is_folder == -1) {
-           child_is_folder = (stat (child, &st) != -1 && S_ISDIR(st.st_mode));
-       }
-       if (child_is_folder) {
-           /* addfold saves child in the list, don't free it */
-           addfold (child);
-       } else {
-           free (child);
-       }
-    }
-
-    closedir (dd);
-    free(prefix);
-}
-
-/*
- * Add the folder name into the
- * list in a sorted fashion.
- */
-
-static void
-addfold (char *fold)
-{
-    register int i, j;
-
-    /* if necessary, reallocate the space for folder names */
-    if (foldp >= maxfolders) {
-       maxfolders += NUMFOLDERS;
-       folds = mh_xrealloc (folds, maxfolders * sizeof(char *));
-    }
-
-    for (i = start; i < foldp; i++)
-       if (compare (fold, folds[i]) < 0) {
-           for (j = foldp - 1; j >= i; j--)
-               folds[j + 1] = folds[j];
-           foldp++;
-           folds[i] = fold;
-           return;
-       }
-
-    folds[foldp++] = fold;
-}
-
-
-static int
-compare (char *s1, char *s2)
-{
-    register int i;
-
-    while (*s1 || *s2)
-       if ((i = *s1++ - *s2++))
-           return i;
-
-    return 0;
-}
-
 /*
  * Do the read only folders
  */

=== added file 'uip/new.c'
--- uip/new.c   1970-01-01 00:00:00 +0000
+++ uip/new.c   2008-08-08 06:43:57 +0000
@@ -0,0 +1,510 @@
+
+/*
+ * new.c -- as new,    list all folders with unseen messages
+ *       -- as fn,     move to next folder with unseen messages
+ *       -- as fp,     move to previous folder with unseen messages
+ *       -- as unseen, scan all unseen messages
+ * $Id$
+ *
+ * This code is Copyright (c) 2008, by the authors of nmh.  See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
+ *
+ * Inspired by Luke Mewburn's new: http://www.mewburn.net/luke/src/new
+ */
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <h/mh.h>
+#include <h/crawl_folders.h>
+#include <h/utils.h>
+
+static struct swit switches[] = {
+#define MODESW 0
+    { "mode", 1 },
+#define FOLDERSSW 1
+    { "folders", 1 },
+#define VERSIONSW 2
+    { "version", 1 },
+#define HELPSW 3
+    { "help", 1 },
+    { NULL, 0 }
+};
+
+static enum { NEW, FN, FP, UNSEEN } run_mode = NEW;
+
+/* check_folders uses this to maintain state with both .folders list of
+ * folders and with crawl_folders. */
+struct list_state {
+    struct node **first, **cur_node;
+    size_t *maxlen;
+    char *cur;
+    char **sequences;
+    struct node *node;
+};
+
+/* Return the number of messages in a string list of message numbers. */
+static int
+count_messages(char *field)
+{
+    int total = 0;
+    int j, k;
+    char *cp, **ap;
+
+    field = getcpy(field);
+
+    /* copied from seq_read.c:seq_init */
+    for (ap = brkstring (field, " ", "\n"); *ap; ap++) {
+        if ((cp = strchr(*ap, '-')))
+            *cp++ = '\0';
+        if ((j = m_atoi (*ap)) > 0) {
+            k = cp ? m_atoi (cp) : j;
+
+            total += k - j + 1;
+        }
+    }
+
+    free(field);
+
+    return total;
+}
+
+/* Return TRUE if the sequence 'name' is in 'sequences'. */
+static boolean
+seq_in_list(char *name, char *sequences[])
+{
+    int i;
+
+    for (i = 0; sequences[i] != NULL; i++) {
+       if (strcmp(name, sequences[i]) == 0) {
+           return TRUE;
+       }
+    }
+
+    return FALSE;
+}
+
+/* Return the string list of message numbers from the sequences file, or NULL
+ * if none. */
+static char *
+get_msgnums(char *folder, char *sequences[])
+{
+    char *seqfile = concat(m_maildir(folder), "/", mh_seq, (void *)NULL);
+    FILE *fp = fopen(seqfile, "r");
+    int state;
+    char name[NAMESZ], field[BUFSIZ];
+    char *cp;
+    char *msgnums = NULL, *this_msgnums, *old_msgnums;
+
+    /* no sequences file -> no messages */
+    if (fp == NULL) {
+        return NULL;
+    }
+
+    /* copied from seq_read.c:seq_public */
+    for (state = FLD;;) {
+        switch (state = m_getfld (state, name, field, sizeof(field), fp)) {
+            case FLD:
+            case FLDPLUS:
+            case FLDEOF:
+                if (state == FLDPLUS) {
+                    cp = getcpy (field);
+                    while (state == FLDPLUS) {
+                        state = m_getfld (state, name, field,
+                                          sizeof(field), fp);
+                        cp = add (field, cp);
+                    }
+
+                    /* Here's where we differ from seq_public: if it's in a
+                    * sequence we want, save the list of messages. */
+                    if (seq_in_list(name, sequences)) {
+                       this_msgnums = trimcpy(cp);
+                       if (msgnums == NULL) {
+                           msgnums = this_msgnums;
+                       } else {
+                           old_msgnums = msgnums;
+                           msgnums = concat(old_msgnums, " ",
+                                            this_msgnums, (void *)NULL);
+                           free(old_msgnums);
+                           free(this_msgnums);
+                       }
+                    }
+                    free (cp);
+                } else {
+                   /* and here */
+                    if (seq_in_list(name, sequences)) {
+                       this_msgnums = trimcpy(field);
+                       if (msgnums == NULL) {
+                           msgnums = this_msgnums;
+                       } else {
+                           old_msgnums = msgnums;
+                           msgnums = concat(old_msgnums, " ",
+                                            this_msgnums, (void *)NULL);
+                           free(old_msgnums);
+                           free(this_msgnums);
+                       }
+                    }
+                }
+
+                if (state == FLDEOF)
+                    break;
+                continue;
+
+            case BODY:
+            case BODYEOF:
+                adios (NULL, "no blank lines are permitted in %s", seqfile);
+                /* fall */
+
+            case FILEEOF:
+                break;
+
+            default:
+                adios (NULL, "%s is poorly formatted", seqfile);
+        }
+        break;  /* break from for loop */
+    }
+
+    fclose(fp);
+
+    return msgnums;
+}
+
+/* Check `folder' (of length `len') for interesting messages, filling in the
+ * list in `b'. */
+static void
+check_folder(char *folder, size_t len, struct list_state *b)
+{
+    char *msgnums = get_msgnums(folder, b->sequences);
+    int is_cur = strcmp(folder, b->cur) == 0;
+
+    if (is_cur || msgnums != NULL) {
+       if (*b->first == NULL) {
+           *b->first = b->node = mh_xmalloc(sizeof(*b->node));
+       } else {
+           b->node->n_next = mh_xmalloc(sizeof(*b->node));
+           b->node = b->node->n_next;
+       }
+       b->node->n_name = folder;
+       b->node->n_field = msgnums;
+
+       if (*b->maxlen < len) {
+           *b->maxlen = len;
+       }
+    }
+
+    /* Save the node for the current folder, so we can fall back to it. */
+    if (is_cur) {
+       *b->cur_node = b->node;
+    }
+}
+
+static boolean
+crawl_callback(char *folder, void *baton)
+{
+    check_folder(folder, strlen(folder), baton);
+    return TRUE;
+}
+
+/* Scan folders, returning:
+ * first        -- list of nodes for all folders which have desired messages;
+ *                 if the current folder is listed in .folders, it is also in
+ *                 the list regardless of whether it has any desired messages
+ * last         -- last node in list
+ * cur_node     -- node of current folder, if listed in .folders
+ * maxlen       -- length of longest folder name
+ *
+ * `cur' points to the name of the current folder, `folders' points to the
+ * name of a .folder (if NULL, crawl all folders), and `sequences' points to
+ * the array of sequences for which to look.
+ */
+static void
+check_folders(struct node **first, struct node **last,
+             struct node **cur_node, size_t *maxlen,
+             char *cur, char *folders, char *sequences[])
+{
+    struct list_state b;
+    FILE *fp;
+    char *line;
+    size_t len;
+
+    *first = *cur_node = NULL;
+    *maxlen = 0;
+
+    b.first = first;
+    b.cur_node = cur_node;
+    b.maxlen = maxlen;
+    b.cur = cur;
+    b.sequences = sequences;
+
+    if (folders == NULL) {
+       chdir(m_maildir(""));
+       crawl_folders(".", crawl_callback, &b);
+    } else {
+       fp = fopen(folders, "r");
+       if (fp  == NULL) {
+           adios(NULL, "failed to read %s", folders);
+       }
+       while (vfgets(fp, &line) == OK) {
+           len = strlen(line) - 1;
+           line[len] = '\0';
+           check_folder(getcpy(line), len, &b);
+       }
+       fclose(fp);
+    }
+
+    if (*first != NULL) {
+       b.node->n_next = NULL;
+       *last = b.node;
+    }
+}
+
+/* Return a single string of the `sequences' joined by a space (' '). */
+static char *
+join_sequences(char *sequences[])
+{
+    int i;
+    size_t len = 0;
+    char *result, *cp;
+
+    for (i = 0; sequences[i] != NULL; i++) {
+       len += strlen(sequences[i]) + 1;
+    }
+    result = mh_xmalloc(len + 1);
+
+    for (i = 0, cp = result; sequences[i] != NULL; i++, cp += len + 1) {
+       len = strlen(sequences[i]);
+       memcpy(cp, sequences[i], len);
+       cp[len] = ' ';
+    }
+    /* -1 to overwrite the last delimiter */
+    *--cp = '\0';
+
+    return result;
+}
+
+/* Return a struct node for the folder to change to.  This is the next
+ * (previous, if FP mode) folder with desired messages, or the current
+ * folder if no folders have desired.  If NEW or UNSEEN mode, print the
+ * output but don't change folders.
+ *
+ * n_name is the folder to change to, and n_field is the string list of
+ * desired message numbers.
+ */
+static struct node *
+doit(char *cur, char *folders, char *sequences[])
+{
+    struct node *first, *cur_node, *node, *last, *prev;
+    size_t folder_len;
+    int count, total = 0;
+    char *command, *sequences_s;
+
+    if (cur == NULL || cur[0] == '\0') {
+        cur = "inbox";
+    }
+
+    check_folders(&first, &last, &cur_node, &folder_len, cur,
+                 folders, sequences);
+    if (first == NULL) {
+       return NULL;
+    }
+
+    if (run_mode == FN || run_mode == FP) {
+       /* For FN or FP, return first now if: */
+           /* we have only one node */
+       if (first->n_next == NULL
+           /* the current folder is not listed in .folders */
+           || cur_node == NULL) {
+           return first;
+       }
+    } else if (run_mode == UNSEEN) {
+       sequences_s = join_sequences(sequences);
+    }
+
+    for (node = first, prev = NULL;
+        node != NULL;
+        prev = node, node = node->n_next) {
+        if (run_mode == FN) {
+            /* If we have a previous node and it is the current
+             * folder, return this node. */
+            if (prev != NULL && strcmp(prev->n_name, cur) == 0) {
+                return node;
+            }
+        } else if (run_mode == FP) {
+            if (strcmp(node->n_name, cur) == 0) {
+                /* Found current folder in fp mode; if we have a
+                 * previous node in the list, return it; else return
+                 * the last node. */
+                if (prev == NULL) {
+                    return last;
+                }
+                return prev;
+            }
+        } else if (run_mode == UNSEEN) {
+            if (node->n_field == NULL) {
+                continue;
+            }
+
+            printf("\n%d %s messages in %s",
+                   count_messages(node->n_field),
+                  sequences_s,
+                   node->n_name);
+            if (strcmp(node->n_name, cur) == 0) {
+                puts(" (*: current folder)");
+            } else {
+                puts("");
+            }
+            fflush(stdout);
+
+           /* TODO: Split enough of scan.c out so that we can call it here. */
+           command = concat("scan +", node->n_name, " ", sequences_s,
+                            (void *)NULL);
+           system(command);
+           free(command);
+        } else {
+            if (node->n_field == NULL) {
+                continue;
+            }
+
+            count = count_messages(node->n_field);
+            total += count;
+
+            printf("%-*s %6d.%c %s\n",
+                   folder_len, node->n_name,
+                   count,
+                   (strcmp(node->n_name, cur) == 0 ? '*' : ' '),
+                   node->n_field);
+        }
+    }
+
+    /* If we're fn, we haven't checked the last node yet.  If it's the
+     * current folder, return the first node. */
+    if (run_mode == FN && strcmp(last->n_name, cur) == 0) {
+        return first;
+    }
+
+    if (run_mode == NEW) {
+        printf("%-*s %6d.\n", folder_len, " total", total);
+    }
+
+    return cur_node;
+}
+
+int
+main(int argc, char **argv)
+{
+    char **ap, *cp, **argp, **arguments;
+    char help[BUFSIZ];
+    char *folders = NULL;
+    char *sequences[NUMATTRS + 1];
+    int i = 0;
+    char *unseen;
+    struct node *folder;
+
+#ifdef LOCALE
+    setlocale(LC_ALL, "");
+#endif
+    invo_name = r1bindex(argv[0], '/');
+
+    /* read user profile/context */
+    context_read();
+
+    arguments = getarguments (invo_name, argc, argv, 1);
+    argp = arguments;
+
+    /*
+     * Parse arguments
+     */
+    while ((cp = *argp++)) {
+       if (*cp == '-') {
+           switch (smatch (++cp, switches)) {
+           case AMBIGSW:
+               ambigsw (cp, switches);
+               done (1);
+           case UNKWNSW:
+               adios (NULL, "-%s unknown", cp);
+
+           case HELPSW:
+               snprintf (help, sizeof(help), "%s [switches] [sequences]",
+                         invo_name);
+               print_help (help, switches, 1);
+               done (1);
+           case VERSIONSW:
+               print_version(invo_name);
+               done (1);
+
+           case FOLDERSSW:
+               if (!(folders = *argp++) || *folders == '-')
+                   adios(NULL, "missing argument to %s", argp[-2]);
+               continue;
+           case MODESW:
+               if (!(invo_name = *argp++) || *invo_name == '-')
+                   adios(NULL, "missing argument to %s", argp[-2]);
+               invo_name = r1bindex(invo_name, '/');
+               continue;
+           }
+       }
+       /* have a sequence argument */
+       if (!seq_in_list(cp, sequences)) {
+           sequences[i++] = cp;
+       }
+    }
+
+    if (strcmp(invo_name, "fn") == 0) {
+        run_mode = FN;
+    } else if (strcmp(invo_name, "fp") == 0) {
+        run_mode = FP;
+    } else if (strcmp(invo_name, "unseen") == 0) {
+        run_mode = UNSEEN;
+    }
+
+    if (folders == NULL) {
+       /* will flists */
+    } else {
+       if (folders[0] != '/') {
+           folders = m_maildir(folders);
+       }
+    }
+
+    if (i == 0) {
+       /* no sequence arguments; use unseen */
+       unseen = context_find(usequence);
+       if (unseen == NULL || unseen[0] == '\0') {
+           adios(NULL, "must specify sequences or set %s", usequence);
+       }
+       for (ap = brkstring(unseen, " ", "\n"); *ap; ap++) {
+           sequences[i++] = *ap;
+       }
+    }
+    sequences[i] = NULL;
+
+    folder = doit(context_find(pfolder), folders, sequences);
+    if (folder == NULL) {
+        done(0);
+        return 1;
+    }
+
+    if (run_mode == UNSEEN) {
+        /* All the scan(1)s it runs change the current folder, so we
+         * need to put it back.  Unfortunately, context_replace lamely
+         * ignores the new value you give it if it is the same one it
+         * has in memory.  So, we'll be lame, too.  I'm not sure if i
+         * should just change context_replace... */
+        context_replace(pfolder, "defeat_context_replace_optimization");
+    }
+
+    /* update current folder */
+    context_replace(pfolder, folder->n_name);
+
+    if (run_mode == FN || run_mode == FP) {
+        printf("%s  %s\n", folder->n_name, folder->n_field);
+    }
+
+    context_save();
+
+    done (0);
+    return 1;
+}



_______________________________________________
Nmh-workers mailing list
Nmh-workers(_at_)nongnu(_dot_)org
http://lists.nongnu.org/mailman/listinfo/nmh-workers

<Prev in Thread] Current Thread [Next in Thread>