]> diplodocus.org Git - nmh/blob - uip/flist.c
seq_setprev.c: Move interface to own file.
[nmh] / uip / flist.c
1 /* flist.c -- list nmh folders containing messages
2 * -- in a given sequence
3 *
4 * originally by
5 * David Nichols, Xerox-PARC, November, 1992
6 *
7 * Copyright (c) 1994 Xerox Corporation.
8 * Use and copying of this software and preparation of derivative works based
9 * upon this software are permitted. Any distribution of this software or
10 * derivative works must comply with all applicable United States export
11 * control laws. This software is made available AS IS, and Xerox Corporation
12 * makes no warranty about the software, its performance or its conformity to
13 * any specification.
14 */
15
16 #include "h/mh.h"
17 #include "sbr/smatch.h"
18 #include "sbr/ssequal.h"
19 #include "sbr/getfolder.h"
20 #include "sbr/folder_read.h"
21 #include "sbr/folder_free.h"
22 #include "sbr/context_save.h"
23 #include "sbr/context_replace.h"
24 #include "sbr/context_find.h"
25 #include "sbr/brkstring.h"
26 #include "sbr/ambigsw.h"
27 #include "sbr/path.h"
28 #include "sbr/print_version.h"
29 #include "sbr/print_help.h"
30 #include "sbr/seq_getnum.h"
31 #include "sbr/error.h"
32 #include "h/utils.h"
33 #include "h/done.h"
34 #include "sbr/m_maildir.h"
35
36 /*
37 * We allocate space to record the names of folders
38 * (foldersToDo array), this number of elements at a time.
39 */
40 #define MAXFOLDERS 100
41
42
43 #define FLIST_SWITCHES \
44 X("sequence name", 0, SEQSW) \
45 X("all", 0, ALLSW) \
46 X("noall", 0, NOALLSW) \
47 X("recurse", 0, RECURSE) \
48 X("norecurse", 0, NORECURSE) \
49 X("showzero", 0, SHOWZERO) \
50 X("noshowzero", 0, NOSHOWZERO) \
51 X("alpha", 0, ALPHASW) \
52 X("noalpha", 0, NOALPHASW) \
53 X("fast", 0, FASTSW) \
54 X("nofast", 0, NOFASTSW) \
55 X("total", -5, TOTALSW) \
56 X("nototal", -7, NOTOTALSW) \
57 X("version", 0, VERSIONSW) \
58 X("help", 0, HELPSW) \
59
60 #define X(sw, minchars, id) id,
61 DEFINE_SWITCH_ENUM(FLIST);
62 #undef X
63
64 #define X(sw, minchars, id) { sw, minchars, id },
65 DEFINE_SWITCH_ARRAY(FLIST, switches);
66 #undef X
67
68 struct Folder {
69 char *name; /* name of folder */
70 int priority;
71 int error; /* error == 1 for unreadable folder */
72 int nMsgs; /* number of messages in folder */
73 ivector_t nSeq; /* number of messages in each sequence */
74 ivector_t private; /* is given sequence, public or private */
75 };
76
77 static struct Folder *orders = NULL;
78 static int nOrders = 0;
79 static int nOrdersAlloced = 0;
80 static struct Folder *folders = NULL;
81 static unsigned int nFolders = 0;
82 static int nFoldersAlloced = 0;
83
84 /* info on folders to search */
85 static char **foldersToDo;
86 static int numfolders;
87 static int maxfolders;
88
89 /* info on sequences to search for */
90 static svector_t sequencesToDo;
91
92 static bool all; /* scan all folders in top level? */
93 static bool alphaOrder; /* want alphabetical order only */
94 static bool recurse; /* show nested folders? */
95 static bool showzero = true; /* show folders even if no messages in seq? */
96 static bool Total = true; /* display info on number of messages in
97 * sequence found, and total num messages */
98
99 static char curfolder[BUFSIZ]; /* name of the current folder */
100 static char *nmhdir; /* base nmh mail directory */
101
102 /*
103 * Type for a compare function for qsort. This keeps
104 * the compiler happy.
105 */
106 typedef int (*qsort_comp) (const void *, const void *);
107
108 /*
109 * static prototypes
110 */
111 static int CompareFolders(struct Folder *, struct Folder *);
112 static void GetFolderOrder(void);
113 static void ScanFolders(void);
114 static int AddFolder(char *, int);
115 static void BuildFolderList(char *, int);
116 static void BuildFolderListRecurse(char *, struct stat *, int);
117 static void PrintFolders(void);
118 static void AllocFolders(struct Folder **, int *, int);
119 static int AssignPriority(char *);
120 static void do_readonly_folders(void);
121
122
123
124 int
125 main(int argc, char **argv)
126 {
127 char *cp, **argp;
128 char **arguments;
129 char buf[BUFSIZ];
130
131 if (nmh_init(argv[0], true, true)) { return 1; }
132
133 /*
134 * If program was invoked with name ending
135 * in `s', then add switch `-all'.
136 */
137 all = has_suffix_c(argv[0], 's');
138
139 arguments = getarguments (invo_name, argc, argv, 1);
140 argp = arguments;
141
142 /* allocate the initial space to record the folder names */
143 numfolders = 0;
144 maxfolders = MAXFOLDERS;
145 foldersToDo = mh_xmalloc ((size_t) (maxfolders * sizeof(*foldersToDo)));
146
147 /* no sequences yet */
148 sequencesToDo = svector_create (0);
149
150 /* parse arguments */
151 while ((cp = *argp++)) {
152 if (*cp == '-') {
153 switch (smatch(++cp, switches)) {
154 case AMBIGSW:
155 ambigsw(cp, switches);
156 done(1);
157 case UNKWNSW:
158 die("-%s unknown", cp);
159
160 case HELPSW:
161 snprintf(buf, sizeof(buf), "%s [+folder1 [+folder2 ...]][switches]",
162 invo_name);
163 print_help(buf, switches, 1);
164 done(0);
165 case VERSIONSW:
166 print_version(invo_name);
167 done (0);
168
169 case SEQSW:
170 if (!(cp = *argp++) || *cp == '-')
171 die("missing argument to %s", argp[-2]);
172
173 svector_push_back (sequencesToDo, cp);
174 break;
175
176 case ALLSW:
177 all = true;
178 break;
179 case NOALLSW:
180 all = false;
181 break;
182
183 case SHOWZERO:
184 showzero = true;
185 break;
186 case NOSHOWZERO:
187 showzero = false;
188 break;
189
190 case ALPHASW:
191 alphaOrder = true;
192 break;
193 case NOALPHASW:
194 alphaOrder = false;
195 break;
196
197 case NOFASTSW:
198 case TOTALSW:
199 Total = true;
200 break;
201
202 case FASTSW:
203 case NOTOTALSW:
204 Total = false;
205 break;
206
207 case RECURSE:
208 recurse = true;
209 break;
210 case NORECURSE:
211 recurse = false;
212 break;
213 }
214 } else {
215 /*
216 * Check if we need to allocate more space
217 * for folder names.
218 */
219 if (numfolders >= maxfolders) {
220 maxfolders += MAXFOLDERS;
221 foldersToDo = mh_xrealloc (foldersToDo,
222 (size_t) (maxfolders * sizeof(*foldersToDo)));
223 }
224 if (*cp == '+' || *cp == '@') {
225 foldersToDo[numfolders++] =
226 pluspath (cp);
227 } else
228 foldersToDo[numfolders++] = cp;
229 }
230 }
231
232 if (!context_find ("path"))
233 free (path ("./", TFOLDER));
234
235 /* get current folder */
236 strncpy (curfolder, getfolder(1), sizeof(curfolder));
237
238 /* get nmh base directory */
239 nmhdir = m_maildir ("");
240
241 /*
242 * If we didn't specify any sequences, we search
243 * for the "Unseen-Sequence" profile entry and use
244 * all the sequences defined there.
245 */
246 if (svector_size (sequencesToDo) == 0) {
247 if ((cp = context_find(usequence)) && *cp) {
248 char **ap, *dp;
249
250 dp = mh_xstrdup(cp);
251 ap = brkstring (dp, " ", "\n");
252 for (; ap && *ap; ap++)
253 svector_push_back (sequencesToDo, *ap);
254 } else {
255 die("no sequence specified or %s profile entry found", usequence);
256 }
257 }
258
259 GetFolderOrder();
260 ScanFolders();
261 qsort(folders, nFolders, sizeof(struct Folder), (qsort_comp) CompareFolders);
262 PrintFolders();
263 svector_free (sequencesToDo);
264 done (0);
265 return 1;
266 }
267
268 /*
269 * Read the Flist-Order profile entry to determine
270 * how to sort folders for output.
271 */
272
273 static void
274 GetFolderOrder(void)
275 {
276 char *p, *s;
277 int priority = 1;
278 struct Folder *o;
279
280 if (!(p = context_find("Flist-Order")))
281 return;
282 for (;;) {
283 while (isspace((unsigned char) *p))
284 ++p;
285 s = p;
286 while (*p && !isspace((unsigned char) *p))
287 ++p;
288 if (p != s) {
289 /* Found one. */
290 AllocFolders(&orders, &nOrdersAlloced, nOrders + 1);
291 o = &orders[nOrders++];
292 o->priority = priority++;
293 o->name = mh_xmalloc(p - s + 1);
294 strncpy(o->name, s, p - s);
295 o->name[p - s] = 0;
296 } else
297 break;
298 }
299 }
300
301 /*
302 * Scan all the necessary folders
303 */
304
305 static void
306 ScanFolders(void)
307 {
308 int i;
309
310 /*
311 * change directory to base of nmh directory
312 */
313 if (chdir (nmhdir) == NOTOK)
314 adios (nmhdir, "unable to change directory to");
315
316 if (numfolders > 0) {
317 /* Update context */
318 strncpy (curfolder, foldersToDo[numfolders - 1], sizeof(curfolder));
319 context_replace (pfolder, curfolder);/* update current folder */
320 context_save (); /* save the context file */
321
322 /*
323 * Scan each given folder. If -all is given,
324 * then also scan the 1st level subfolders under
325 * each given folder.
326 */
327 for (i = 0; i < numfolders; ++i)
328 BuildFolderList(foldersToDo[i], all ? 1 : 0);
329 } else {
330 if (all) {
331 /*
332 * Do the readonly folders
333 */
334 do_readonly_folders();
335
336 /*
337 * Now scan the entire nmh directory for folders
338 */
339 BuildFolderList(".", 0);
340 } else {
341 /*
342 * Else scan current folder
343 */
344 BuildFolderList(curfolder, 0);
345 }
346 }
347 }
348
349 /*
350 * Initial building of folder list for
351 * the top of our search tree.
352 */
353
354 static void
355 BuildFolderList(char *dirName, int searchdepth)
356 {
357 struct stat st;
358
359 /* Make sure we have a directory */
360 if ((stat(dirName, &st) == -1) || !S_ISDIR(st.st_mode))
361 return;
362
363 /*
364 * If base directory, don't add it to the
365 * folder list. We just recurse into it.
366 */
367 if (!strcmp (dirName, ".")) {
368 BuildFolderListRecurse (".", &st, 0);
369 return;
370 }
371
372 /*
373 * Add this folder to the list.
374 * If recursing and directory has subfolders,
375 * then build folder list for subfolders.
376 */
377 if (AddFolder(dirName, showzero) && (recurse || searchdepth) && st.st_nlink > 2)
378 BuildFolderListRecurse(dirName, &st, searchdepth - 1);
379 }
380
381 /*
382 * Recursive building of folder list
383 */
384
385 static void
386 BuildFolderListRecurse(char *dirName, struct stat *s, int searchdepth)
387 {
388 char *base, name[PATH_MAX];
389 char *n;
390 int nlinks;
391 DIR *dir;
392 struct dirent *dp;
393 struct stat st;
394
395 /*
396 * Keep track of number of directories we've seen so we can
397 * stop stat'ing entries in this directory once we've seen
398 * them all. This optimization will fail if you have extra
399 * directories beginning with ".", since we don't bother to
400 * stat them. But that shouldn't generally be a problem.
401 */
402 nlinks = s->st_nlink;
403 if (nlinks == 1) {
404 /* Disable the optimization under conditions where st_nlink
405 is set to 1. That happens on Cygwin, for example:
406 http://cygwin.com/ml/cygwin-apps/2008-08/msg00264.html */
407 nlinks = INT_MAX;
408 }
409
410 if (!(dir = opendir(dirName)))
411 adios(dirName, "can't open directory");
412
413 /*
414 * A hack so that we don't see a
415 * leading "./" in folder names.
416 */
417 base = strcmp (dirName, ".") ? dirName : dirName + 1;
418
419 while (nlinks && (dp = readdir(dir))) {
420 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) {
421 nlinks--;
422 continue;
423 }
424 if (dp->d_name[0] == '.')
425 continue;
426 /* Check to see if the name of the file is a number
427 * if it is, we assume it's a mail file and skip it
428 */
429 for (n = dp->d_name; isdigit((unsigned char)*n); n++);
430 if (!*n)
431 continue;
432 strncpy (name, base, sizeof(name) - 2);
433 if (*base)
434 strcat(name, "/");
435 strncat(name, dp->d_name, sizeof(name) - strlen(name) - 1);
436 if ((stat(name, &st) != -1) && S_ISDIR(st.st_mode)) {
437 /*
438 * Check if this was really a symbolic link pointing
439 * to a directory. If not, then decrement link count.
440 */
441 if (lstat (name, &st) == -1)
442 nlinks--;
443 /* Add this folder to the list */
444 if (AddFolder(name, showzero) &&
445 (recurse || searchdepth) && st.st_nlink > 2)
446 BuildFolderListRecurse(name, &st, searchdepth - 1);
447 }
448 }
449 closedir(dir);
450 }
451
452 /*
453 * Add this folder to our list, counting the total number of
454 * messages and the number of messages in each sequence.
455 */
456
457 static int
458 AddFolder(char *name, int force)
459 {
460 unsigned int i;
461 int msgnum;
462 bool nonzero;
463 ivector_t seqnum = ivector_create (0), nSeq = ivector_create (0);
464 struct Folder *f;
465 struct msgs *mp;
466 char *cp;
467
468 /* Read folder and create message structure */
469 if (!(mp = folder_read (name, 0))) {
470 /* Oops, error occurred. Record it and continue. */
471 AllocFolders(&folders, &nFoldersAlloced, nFolders + 1);
472 f = &folders[nFolders++];
473 f->name = mh_xstrdup(name);
474 f->error = 1;
475 f->priority = AssignPriority(f->name);
476 return 0;
477 }
478
479 for (i = 0; i < svector_size (sequencesToDo); i++) {
480 /* Convert sequences to their sequence numbers */
481 if ((cp = svector_at (sequencesToDo, i)))
482 ivector_push_back (seqnum, seq_getnum(mp, cp));
483 else
484 ivector_push_back (seqnum, -1);
485
486 /* Now count messages in this sequence */
487 ivector_push_back (nSeq, 0);
488 if (mp->nummsg > 0 && ivector_at (seqnum, i) != -1) {
489 for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) {
490 if (in_sequence(mp, ivector_at (seqnum, i), msgnum))
491 (*ivector_atp (nSeq, i))++;
492 }
493 }
494 }
495
496 /* Check if any of the sequence checks were nonzero */
497 nonzero = false;
498 for (i = 0; i < svector_size (sequencesToDo); i++) {
499 if (ivector_at (nSeq, i) > 0) {
500 nonzero = true;
501 break;
502 }
503 }
504
505 if (nonzero || force) {
506 /* save general folder information */
507 AllocFolders(&folders, &nFoldersAlloced, nFolders + 1);
508 f = &folders[nFolders++];
509 f->name = mh_xstrdup(name);
510 f->nMsgs = mp->nummsg;
511 f->nSeq = ivector_create (0);
512 f->private = ivector_create (0);
513 f->error = 0;
514 f->priority = AssignPriority(f->name);
515
516 /* record the sequence information */
517 for (i = 0; i < svector_size (sequencesToDo); i++) {
518 *ivector_atp (f->nSeq, i) = ivector_at (nSeq, i);
519 ivector_push_back (f->private,
520 ivector_at (seqnum, i) != -1
521 ? is_seq_private(mp, ivector_at (seqnum, i))
522 : 0);
523 }
524 }
525
526 ivector_free (nSeq);
527 ivector_free (seqnum);
528 folder_free (mp); /* free folder/message structure */
529 return 1;
530 }
531
532 /*
533 * Print the folder/sequence information
534 */
535
536 static void
537 PrintFolders(void)
538 {
539 char tmpname[BUFSIZ];
540 unsigned int i, j, len;
541 bool has_private = false;
542 unsigned int maxfolderlen = 0, maxseqlen = 0;
543 int maxnum = 0, maxseq = 0;
544
545 if (!Total) {
546 for (i = 0; i < nFolders; i++)
547 puts(folders[i].name);
548 return;
549 }
550
551 /*
552 * Find the width we need for various fields
553 */
554 for (i = 0; i < nFolders; ++i) {
555 /* find the length of longest folder name */
556 len = strlen(folders[i].name);
557 if (len > maxfolderlen)
558 maxfolderlen = len;
559
560 /* If folder had error, skip the rest */
561 if (folders[i].error)
562 continue;
563
564 /* find the maximum total messages */
565 if (folders[i].nMsgs > maxnum)
566 maxnum = folders[i].nMsgs;
567
568 for (j = 0; j < svector_size (sequencesToDo); j++) {
569 /* find maximum width of sequence name */
570 len = strlen (svector_at (sequencesToDo, j));
571 if ((ivector_at (folders[i].nSeq, j) > 0 || showzero) &&
572 (len > maxseqlen))
573 maxseqlen = len;
574
575 /* find the maximum number of messages in sequence */
576 if (ivector_at (folders[i].nSeq, j) > maxseq)
577 maxseq = ivector_at (folders[i].nSeq, j);
578
579 /* check if this sequence is private in any of the folders */
580 if (ivector_at (folders[i].private, j))
581 has_private = true;
582 }
583 }
584
585 /* Now print all the folder/sequence information */
586 for (i = 0; i < nFolders; i++) {
587 for (j = 0; j < svector_size (sequencesToDo); j++) {
588 if (ivector_at (folders[i].nSeq, j) > 0 || showzero) {
589 /* Add `+' to end of name of current folder */
590 if (strcmp(curfolder, folders[i].name))
591 snprintf(tmpname, sizeof(tmpname), "%s", folders[i].name);
592 else
593 snprintf(tmpname, sizeof(tmpname), "%s+", folders[i].name);
594
595 if (folders[i].error) {
596 printf("%-*s is unreadable\n", maxfolderlen+1, tmpname);
597 continue;
598 }
599
600 printf("%-*s has %*d in sequence %-*s%s; out of %*d\n",
601 maxfolderlen+1, tmpname,
602 num_digits(maxseq), ivector_at (folders[i].nSeq, j),
603 maxseqlen, svector_at (sequencesToDo, j),
604 !has_private ? "" : ivector_at (folders[i].private, j)
605 ? " (private)" : " ",
606 num_digits(maxnum), folders[i].nMsgs);
607 }
608 }
609 }
610 }
611
612 /*
613 * Put them in priority order.
614 */
615
616 static int
617 CompareFolders(struct Folder *f1, struct Folder *f2)
618 {
619 if (!alphaOrder && f1->priority != f2->priority)
620 return f1->priority - f2->priority;
621 return strcmp(f1->name, f2->name);
622 }
623
624 /*
625 * Make sure we have at least n folders allocated.
626 */
627
628 static void
629 AllocFolders(struct Folder **f, int *nfa, int n)
630 {
631 if (n <= *nfa)
632 return;
633 if (*f == NULL) {
634 *nfa = 10;
635 *f = mh_xmalloc (*nfa * (sizeof(struct Folder)));
636 } else {
637 *nfa *= 2;
638 *f = mh_xrealloc (*f, *nfa * (sizeof(struct Folder)));
639 }
640 }
641
642 /*
643 * Return the priority for a name. The highest comes from an exact match.
644 * After that, the longest match (then first) assigns the priority.
645 */
646 static int
647 AssignPriority(char *name)
648 {
649 int i, ol, nl;
650 int best = nOrders;
651 int bestLen = 0;
652 struct Folder *o;
653
654 nl = strlen(name);
655 for (i = 0; i < nOrders; ++i) {
656 o = &orders[i];
657 if (!strcmp(name, o->name))
658 return o->priority;
659 ol = strlen(o->name);
660 if (nl < ol - 1)
661 continue;
662 if (ol < bestLen)
663 continue;
664 if (o->name[0] == '*' && !strcmp(o->name + 1, name + (nl - ol + 1))) {
665 best = o->priority;
666 bestLen = ol;
667 } else if (o->name[ol - 1] == '*' && strncmp(o->name, name, ol - 1) == 0) {
668 best = o->priority;
669 bestLen = ol;
670 }
671 }
672 return best;
673 }
674
675 /*
676 * Do the read only folders
677 */
678
679 static void
680 do_readonly_folders (void)
681 {
682 int atrlen;
683 char atrcur[BUFSIZ];
684 struct node *np;
685
686 snprintf (atrcur, sizeof(atrcur), "atr-%s-", current);
687 atrlen = strlen (atrcur);
688
689 for (np = m_defs; np; np = np->n_next)
690 if (ssequal (atrcur, np->n_name)
691 && !ssequal (nmhdir, np->n_name + atrlen))
692 BuildFolderList (np->n_name + atrlen, 0);
693 }