From 788c353a8bee07520ae1a1de6fb7bdcdf1f4f1c7 Mon Sep 17 00:00:00 2001 From: Eric Gillespie Date: Sat, 17 Jan 2009 16:47:29 +0000 Subject: [PATCH] * 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). --- ChangeLog | 21 ++ h/Makefile.in | 2 +- h/crawl_folders.h | 18 ++ man/.cvsignore | 4 + man/Makefile.in | 1 + man/fnext.man | 1 + man/fprev.man | 1 + man/new.man | 110 ++++++++ man/unseen.man | 1 + sbr/Makefile.in | 3 +- sbr/crawl_folders.c | 151 +++++++++++ test/runtest | 7 +- test/setup-test | 2 - test/tests/inc/test-deb359167 | 2 +- test/tests/inc/test-eom-align | 2 +- test/tests/manpages/test-manpages | 2 +- test/tests/new/test-basic | 164 ++++++++++++ uip/Makefile.in | 13 +- uip/folder.c | 181 +++---------- uip/new.c | 511 +++++++++++++++++++++++++++++++++++++ 20 files changed, 1040 insertions(+), 157 deletions(-) create mode 100644 h/crawl_folders.h create mode 100644 man/fnext.man create mode 100644 man/fprev.man create mode 100644 man/new.man create mode 100644 man/unseen.man create mode 100644 sbr/crawl_folders.c create mode 100644 test/tests/new/test-basic create mode 100644 uip/new.c diff --git a/ChangeLog b/ChangeLog index 5817d9a..b2ea1aa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2009-01-16 Eric Gillespie + + * 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 * sbr/m_getfld.c: fix two bugs which could cause us to walk off diff --git a/h/Makefile.in b/h/Makefile.in index 492d364..7b4a276 100644 --- a/h/Makefile.in +++ b/h/Makefile.in @@ -10,7 +10,7 @@ srcdir = @srcdir@ 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 diff --git a/h/crawl_folders.h b/h/crawl_folders.h new file mode 100644 index 0000000..d04287a --- /dev/null +++ b/h/crawl_folders.h @@ -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); diff --git a/man/.cvsignore b/man/.cvsignore index 1cd0b60..cacddcd 100644 --- a/man/.cvsignore +++ b/man/.cvsignore @@ -10,9 +10,11 @@ dp.8 flist.1 flists.1 fmtdump.8 +fnext.1 folder.1 folders.1 forw.1 +fprev.1 inc.1 install-mh.1 man.sed @@ -37,6 +39,7 @@ mhstore.1 msgchk.1 msh.1 mts.conf.5 +new.1 next.1 nmh.1 packf.1 @@ -58,5 +61,6 @@ sendfiles.1 show.1 slocal.1 sortm.1 +unseen.1 whatnow.1 whom.1 diff --git a/man/Makefile.in b/man/Makefile.in index fe70265..ab93318 100644 --- a/man/Makefile.in +++ b/man/Makefile.in @@ -57,6 +57,7 @@ MAN1SRC = ali. anno. burst. comp. \ 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. \ diff --git a/man/fnext.man b/man/fnext.man new file mode 100644 index 0000000..7529017 --- /dev/null +++ b/man/fnext.man @@ -0,0 +1 @@ +.so man1/new.1 diff --git a/man/fprev.man b/man/fprev.man new file mode 100644 index 0000000..7529017 --- /dev/null +++ b/man/fprev.man @@ -0,0 +1 @@ +.so man1/new.1 diff --git a/man/new.man b/man/new.man new file mode 100644 index 0000000..c724d3e --- /dev/null +++ b/man/new.man @@ -0,0 +1,110 @@ +.\" +.\" %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). diff --git a/man/unseen.man b/man/unseen.man new file mode 100644 index 0000000..7529017 --- /dev/null +++ b/man/unseen.man @@ -0,0 +1 @@ +.so man1/new.1 diff --git a/sbr/Makefile.in b/sbr/Makefile.in index a920958..b8787d1 100644 --- a/sbr/Makefile.in +++ b/sbr/Makefile.in @@ -58,7 +58,8 @@ SRCS = addrsbr.c ambigsw.c atooi.c brkstring.c \ 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 \ diff --git a/sbr/crawl_folders.c b/sbr/crawl_folders.c new file mode 100644 index 0000000..4889bf1 --- /dev/null +++ b/sbr/crawl_folders.c @@ -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 +#include +#include + +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); +} diff --git a/test/runtest b/test/runtest index 4ec4a03..0b18653 100755 --- a/test/runtest +++ b/test/runtest @@ -13,15 +13,18 @@ if [ ! -e "$MH_TEST_DIR/bld/Makefile" ]; then 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`; diff --git a/test/setup-test b/test/setup-test index a99c6c7..fab26a0 100755 --- a/test/setup-test +++ b/test/setup-test @@ -20,5 +20,3 @@ mkdir $TEMPDIR/bld cd $TEMPDIR/bld $srcdir/configure --prefix=$TEMPDIR --with-locking=fcntl --enable-debug make install - -echo "Path: $TEMPDIR/Mail" > $TEMPDIR/mh_profile diff --git a/test/tests/inc/test-deb359167 b/test/tests/inc/test-deb359167 index 61d3283..36cd1c2 100644 --- a/test/tests/inc/test-deb359167 +++ b/test/tests/inc/test-deb359167 @@ -2,7 +2,7 @@ # Test a variant of a mailbox which caused debian bug 359167. set -e -. common.sh +. $MH_TEST_COMMON THISDIR="tests/inc" diff --git a/test/tests/inc/test-eom-align b/test/tests/inc/test-eom-align index 5d197ff..3b6c677 100644 --- a/test/tests/inc/test-eom-align +++ b/test/tests/inc/test-eom-align @@ -4,7 +4,7 @@ set -e -. common.sh +. $MH_TEST_COMMON THISDIR="tests/inc" diff --git a/test/tests/manpages/test-manpages b/test/tests/manpages/test-manpages index 2ad77d8..f7acc69 100644 --- a/test/tests/manpages/test-manpages +++ b/test/tests/manpages/test-manpages @@ -6,7 +6,7 @@ # ###################################################### -. common.sh +. $MH_TEST_COMMON require_prog groff diff --git a/test/tests/new/test-basic b/test/tests/new/test-basic new file mode 100644 index 0000000..772b72f --- /dev/null +++ b/test/tests/new/test-basic @@ -0,0 +1,164 @@ +#!/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 < $expected < $actual 2>&1 +check + +# test with no desired messages +cat > $expected < $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 < $actual 2>&1 +check + +# test with .folders +cat > $expected < $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 < $actual 2>&1 +check + +# set aseq bseq as unseen +echo 'Unseen-Sequence: aseq bseq' >> $MH +new > $actual 2>&1 +check + +# test unseen +cat > $expected <> + 3 09/29 Test3 Testing message 3<> + 4 09/29 Test4 Testing message 4<> + +3 aseq bseq messages in foo2 + 1 09/29 Test1 Testing message 1<> + 3 09/29 Test3 Testing message 3<> + 4 09/29 Test4 Testing message 4<> + +3 aseq bseq messages in inbox (*: current folder) + 1 09/29 Test1 Testing message 1<> + 3 09/29 Test3 Testing message 3<> + 4 09/29 Test4 Testing message 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 < $expected +fnext -folders $folders > $actual 2>&1 +check + +exit $failed diff --git a/uip/Makefile.in b/uip/Makefile.in index f101a28..1e1ae9b 100644 --- a/uip/Makefile.in +++ b/uip/Makefile.in @@ -60,7 +60,7 @@ SETGID_MAIL = @SETGID_MAIL@ # 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 @@ SRCS = ali.c aliasbr.c anno.c annosbr.c ap.c burst.c comp.c \ 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 \ @@ -183,6 +183,9 @@ msgchk: msgchk.o $(POPLIB) $(LOCALLIBS) 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-cmds: 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 diff --git a/uip/folder.c b/uip/folder.c index a94f131..f83bedc 100644 --- a/uip/folder.c +++ b/uip/folder.c @@ -6,12 +6,13 @@ * * $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 +#include #include #include @@ -78,22 +79,10 @@ static int all = 0; /* should we output all folders */ 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 int maxFolderInfo; /* * 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 @@ main (int argc, char **argv) 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 @@ main (int argc, char **argv) /* * 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 @@ main (int argc, char **argv) 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 @@ main (int argc, char **argv) * 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 @@ main (int argc, char **argv) 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 @@ get_folder_info (char *fold, char *msg) * for folder information */ if (total_folders >= maxFolderInfo) { - maxFolderInfo += NUMFOLDERS; + maxFolderInfo += CRAWL_NUMFOLDERS; fi = mh_xrealloc (fi, maxFolderInfo * sizeof(*fi)); } @@ -493,8 +451,32 @@ get_folder_info (char *fold, char *msg) 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 @@ sfold (struct msgs *mp, char *msg) } -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 */ diff --git a/uip/new.c b/uip/new.c new file mode 100644 index 0000000..4ff8c04 --- /dev/null +++ b/uip/new.c @@ -0,0 +1,511 @@ + +/* + * 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 + +#include +#include +#include + +#include +#include +#include + +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; +} -- 1.7.10.4