+2009-01-16 Eric Gillespie <epg@pretzelnet.org>
+
+ * test/runtest, test/tests/inc/test-deb359167,
+ test/tests/inc/test-eom-align, test/tests/manpages/test-manpages:
+ Load common.sh via absolute path, otherwise some /bin/sh
+ (e.g. dash) can't load it.
+
+ * 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.
+
+ * 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-12-26 Peter Maydell <pmaydell@chiark.greenend.org.uk>
* sbr/m_getfld.c: fix two bugs which could cause us to walk off
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
--- /dev/null
+
+/*
+ * 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);
flist.1
flists.1
fmtdump.8
+fnext.1
folder.1
folders.1
forw.1
+fprev.1
inc.1
install-mh.1
man.sed
msgchk.1
msh.1
mts.conf.5
+new.1
next.1
nmh.1
packf.1
show.1
slocal.1
sortm.1
+unseen.1
whatnow.1
whom.1
nmh. mhbuild. mhl. mhlist. mhmail. \
mhn. mhparam. mhpath. mhshow. \
mhstore. msgchk. msh. \
+ new. fnext. fprev. unseen. \
next. packf. pick. prev. \
prompter. rcvdist. rcvpack. \
rcvstore. rcvtty. refile. \
--- /dev/null
+.so man1/new.1
--- /dev/null
+.so man1/new.1
--- /dev/null
+.\"
+.\" %nmhwarning%
+.\" $Id$
+.\"
+.TH NEW %manext1% "%nmhdate%" MH.6.8 [%nmhversion%]
+
+.SH NAME
+new \- report on folders with new messages
+.PP
+fnext \- set current folder to next folder with new messages
+.PP
+fprev \- set current folder to previous folder with new messages
+.PP
+unseen \- scan new messages in all folders with new messages
+
+.SH SYNOPSIS
+.HP 5
+.na
+.B new
+.RI [ sequences ]
+.RB [ \-mode
+.IR mode ]
+.RB [ \-folders
+.IR foldersfile ]
+.RB [ \-version ]
+.RB [ \-help ]
+.PP
+.HP 5
+.B fnext
+is equivalent to
+.B new \-mode fnext
+.PP
+.HP 5
+.B fprev
+is equivalent to
+.B new \-mode fprev
+.PP
+.HP 5
+.B unseen
+is equivalent to
+.B new \-mode unseen
+.ad
+
+.SH DESCRIPTION
+.B New
+in its default mode produces a one\-line\-per\-folder listing of all
+folders containing messages in the listed
+.IR sequences
+or in the sequences listed in the profile entry
+.RI \*(lq Unseen-Sequence \*(rq.
+Each line contains the folder, the number of messages in the desired
+sequences, and the message lists from the .mh_sequences file. For example:
+.PP
+.RS 5
+.nf
+foo 11.* 40\-50
+bar 380. 760\-772 824\-828
+ total 391.
+.fi
+.RE
+.PP
+The `*' on foo indicates that it is the current folder. The last line shows
+the total number of messages in the desired sequences.
+.PP
+.B New
+crawls the folder hierarchy recursively to find all folders, and prints them
+in lexicographic order. Override this behavior by providing
+.IR foldersfile
+containing the pre-sorted list of folders
+.B new
+should check, one per line.
+.PP
+In
+.B fnext
+and
+.B fprev
+modes,
+.B new
+instead changes to the next or previous matching folder, respectively.
+.PP
+In
+.B unseen
+mode,
+.B new
+executes
+.B scan sequences
+for each matching folder.
+
+.SH FILES
+.fc ^ ~
+.nf
+.ta \w'%etcdir%/ExtraBigFileName 'u
+^$HOME/\&.mh\(ruprofile~^The user profile
+.fi
+
+.SH "PROFILE COMPONENTS"
+.fc ^ ~
+.nf
+.ta 2.4i
+.ta \w'ExtraBigProfileName 'u
+^Path:~^To determine the user's nmh directory
+^Current\-Folder:~^To find the default current folder
+^Unseen-Sequence:~^The name of the unseen message sequence
+.fi
+
+.SH "SEE ALSO"
+scan(1), mh\-format(5)
+
+.SH HISTORY
+Based on Luke Mewburn's new (http://www.mewburn.net/luke/src/new).
--- /dev/null
+.so man1/new.1
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 \
--- /dev/null
+
+/*
+ * 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);
+}
echo "temporary directory missing or broken: running setup-test"
./setup-test
export MH_TEST_DIR=`cat test-temp-dir`
-fi
+fi
-export MH=$MH_TEST_DIR/mh_profile
+export MH=$MH_TEST_DIR/Mail/.mh_profile
export PATH=$MH_TEST_DIR/bin:$PATH
+export MH_TEST_COMMON=$PWD/common.sh
+
# 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`;
cd $TEMPDIR/bld
$srcdir/configure --prefix=$TEMPDIR --with-locking=fcntl --enable-debug
make install
-
-echo "Path: $TEMPDIR/Mail" > $TEMPDIR/mh_profile
# Test a variant of a mailbox which caused debian bug 359167.
set -e
-. common.sh
+. $MH_TEST_COMMON
THISDIR="tests/inc"
set -e
-. common.sh
+. $MH_TEST_COMMON
THISDIR="tests/inc"
#
######################################################
-. common.sh
+. $MH_TEST_COMMON
require_prog groff
--- /dev/null
+#!/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
+check
+
+# test with no desired messages
+cat > $expected <<EOF
+ total 0.
+EOF
+new aseq > $actual 2>&1
+check
+new -folders $folders aseq > $actual 2>&1
+check
+
+# test fnext/fprev with no desired messages
+cat /dev/null > $expected
+fnext aseq > $actual 2>&1
+check
+fprev 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 -folders $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 fnext with the current folder not in the list
+echo 'Current-Folder: foo1' > $MH_TEST_DIR/Mail/context
+echo 'inbox 1 3-4' > $expected
+fnext -folders $folders > $actual 2>&1
+check
+
+# test fprev with the current folder not in the list
+echo 'Current-Folder: foo1' > $MH_TEST_DIR/Mail/context
+echo 'inbox 1 3-4' > $expected
+fprev -folders $folders > $actual 2>&1
+check
+
+# test fnext with current folder in the middle of the list
+echo 'Current-Folder: foo2' > $MH_TEST_DIR/Mail/context
+echo 'inbox 1 3-4' > $expected
+fnext > $actual 2>&1
+check
+
+# test fprev with current folder in the middle of the list
+echo 'Current-Folder: foo2' > $MH_TEST_DIR/Mail/context
+echo 'foo1 1 3-4' > $expected
+fprev > $actual 2>&1
+check
+
+# test fprev with current folder at the beginning of the list
+echo 'Current-Folder: foo1' > $MH_TEST_DIR/Mail/context
+echo 'inbox 1 3-4' > $expected
+fprev > $actual 2>&1
+check
+
+# test fnext with current folder at the end of the list
+echo 'Current-Folder: inbox' > $MH_TEST_DIR/Mail/context
+echo 'foo1 1 3-4' > $expected
+fnext > $actual 2>&1
+check
+
+# test fnext with no current folder
+rm $MH_TEST_DIR/Mail/context
+echo 'foo1 1 3-4' > $expected
+fnext > $actual 2>&1
+check
+
+# test fnext with only one folder in the list
+cat > $folders <<EOF
+inbox
+EOF
+echo 'inbox 1 3-4' > $expected
+fnext -folders $folders > $actual 2>&1
+check
+
+exit $failed
# 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
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 popsbr.c \
+ msgchk.c msh.c mshcmds.c new.c packf.c pick.c picksbr.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 \
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)
install-lcmds:
rm -f $(DESTDIR)$(bindir)/flists
rm -f $(DESTDIR)$(bindir)/folders
+ rm -f $(DESTDIR)$(bindir)/fnext
+ rm -f $(DESTDIR)$(bindir)/fprev
+ 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)/fnext
+ $(LN) $(DESTDIR)$(bindir)/new $(DESTDIR)$(bindir)/fprev
+ $(LN) $(DESTDIR)$(bindir)/new $(DESTDIR)$(bindir)/unseen
$(LN) $(DESTDIR)$(bindir)/show $(DESTDIR)$(bindir)/prev
$(LN) $(DESTDIR)$(bindir)/show $(DESTDIR)$(bindir)/next
*
* $Id$
*
- * 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>
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.
/*
* 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);
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));
/*
/*
* 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) {
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)) {
* 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));
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;
* for folder information
*/
if (total_folders >= maxFolderInfo) {
- maxFolderInfo += NUMFOLDERS;
+ maxFolderInfo += CRAWL_NUMFOLDERS;
fi = mh_xrealloc (fi, maxFolderInfo * sizeof(*fi));
}
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;
}
}
-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
*/
--- /dev/null
+
+/*
+ * new.c -- as new, list all folders with unseen messages
+ * -- as fnext, move to next folder with unseen messages
+ * -- as fprev, 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, FNEXT, FPREV, 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 FPREV 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 (run_mode == FNEXT || run_mode == FPREV) {
+ if (first->n_next == NULL) {
+ /* We have only one node; any desired messages in it? */
+ if (first->n_field == NULL) {
+ return NULL;
+ } else {
+ return first;
+ }
+ } else if (cur_node == NULL) {
+ /* Current folder is not listed in .folders, return first. */
+ 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 == FNEXT) {
+ /* 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 == FPREV) {
+ if (strcmp(node->n_name, cur) == 0) {
+ /* Found current folder in fprev 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 fnext, we haven't checked the last node yet. If it's the
+ * current folder, return the first node. */
+ if (run_mode == FNEXT && 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, "fnext") == 0) {
+ run_mode = FNEXT;
+ } else if (strcmp(invo_name, "fprev") == 0) {
+ run_mode = FPREV;
+ } 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 == FNEXT || run_mode == FPREV) {
+ printf("%s %s\n", folder->n_name, folder->n_field);
+ }
+
+ context_save();
+
+ done (0);
+ return 1;
+}