]> diplodocus.org Git - nmh/blob - uip/folder.c
Added support for optional Content_Disposition header in mhbuild directive.s
[nmh] / uip / folder.c
1
2 /*
3 * folder(s).c -- set/list the current message and/or folder
4 * -- push/pop a folder onto/from the folder stack
5 * -- list the folder stack
6 *
7 * $Id$
8 *
9 * This code is Copyright (c) 2002, by the authors of nmh. See the
10 * COPYRIGHT file in the root directory of the nmh distribution for
11 * complete copyright information.
12 */
13
14 #include <h/mh.h>
15 #include <h/utils.h>
16 #include <errno.h>
17
18 static struct swit switches[] = {
19 #define ALLSW 0
20 { "all", 0 },
21 #define NALLSW 1
22 { "noall", 0 },
23 #define CREATSW 2
24 { "create", 0 },
25 #define NCREATSW 3
26 { "nocreate", 0 },
27 #define FASTSW 4
28 { "fast", 0 },
29 #define NFASTSW 5
30 { "nofast", 0 },
31 #define HDRSW 6
32 { "header", 0 },
33 #define NHDRSW 7
34 { "noheader", 0 },
35 #define PACKSW 8
36 { "pack", 0 },
37 #define NPACKSW 9
38 { "nopack", 0 },
39 #define VERBSW 10
40 { "verbose", 0 },
41 #define NVERBSW 11
42 { "noverbose", 0 },
43 #define RECURSW 12
44 { "recurse", 0 },
45 #define NRECRSW 13
46 { "norecurse", 0 },
47 #define TOTALSW 14
48 { "total", 0 },
49 #define NTOTLSW 15
50 { "nototal", 0 },
51 #define LISTSW 16
52 { "list", 0 },
53 #define NLISTSW 17
54 { "nolist", 0 },
55 #define PRNTSW 18
56 { "print", 0 },
57 #define NPRNTSW 19
58 { "noprint", -4 },
59 #define PUSHSW 20
60 { "push", 0 },
61 #define POPSW 21
62 { "pop", 0 },
63 #define VERSIONSW 22
64 { "version", 0 },
65 #define HELPSW 23
66 { "help", 0 },
67 { NULL, 0 }
68 };
69
70 static int fshort = 0; /* output only folder names */
71 static int fcreat = 0; /* should we ask to create new folders? */
72 static int fpack = 0; /* are we packing the folder? */
73 static int fverb = 0; /* print actions taken while packing folder */
74 static int fheader = 0; /* should we output a header? */
75 static int frecurse = 0; /* recurse through subfolders */
76 static int ftotal = 0; /* should we output the totals? */
77 static int all = 0; /* should we output all folders */
78
79 static int total_folders = 0; /* total number of folders */
80
81 static int start = 0;
82 static int foldp = 0;
83
84 static char *nmhdir;
85 static char *stack = "Folder-Stack";
86 static char folder[BUFSIZ];
87
88 #define NUMFOLDERS 100
89
90 /*
91 * This is how many folders we currently can hold in the array
92 * `folds'. We increase this amount by NUMFOLDERS at a time.
93 */
94 static int maxfolders;
95 static char **folds;
96
97 /*
98 * Structure to hold information about
99 * folders as we scan them.
100 */
101 struct FolderInfo {
102 char *name;
103 int nummsg;
104 int curmsg;
105 int lowmsg;
106 int hghmsg;
107 int others; /* others == 1 if other files in folder */
108 int error; /* error == 1 for unreadable folder */
109 };
110
111 /*
112 * Dynamically allocated space to hold
113 * all the folder information.
114 */
115 static struct FolderInfo *fi;
116 static int maxFolderInfo;
117
118 /*
119 * static prototypes
120 */
121 static void dodir (char *);
122 static int get_folder_info (char *, char *);
123 static void print_folders (void);
124 static int num_digits (int);
125 static int sfold (struct msgs *, char *);
126 static void addir (char *);
127 static void addfold (char *);
128 static int compare (char *, char *);
129 static void readonly_folders (void);
130
131
132 int
133 main (int argc, char **argv)
134 {
135 int printsw = 0, listsw = 0;
136 int pushsw = 0, popsw = 0;
137 char *cp, *dp, *msg = NULL, *argfolder = NULL;
138 char **ap, **argp, buf[BUFSIZ], **arguments;
139 struct stat st;
140
141 #ifdef LOCALE
142 setlocale(LC_ALL, "");
143 #endif
144 invo_name = r1bindex (argv[0], '/');
145
146 /* read user profile/context */
147 context_read();
148
149 /*
150 * If program was invoked with name ending
151 * in `s', then add switch `-all'.
152 */
153 if (argv[0][strlen (argv[0]) - 1] == 's')
154 all = 1;
155
156 arguments = getarguments (invo_name, argc, argv, 1);
157 argp = arguments;
158
159 while ((cp = *argp++)) {
160 if (*cp == '-') {
161 switch (smatch (++cp, switches)) {
162 case AMBIGSW:
163 ambigsw (cp, switches);
164 done (1);
165 case UNKWNSW:
166 adios (NULL, "-%s unknown", cp);
167
168 case HELPSW:
169 snprintf (buf, sizeof(buf), "%s [+folder] [msg] [switches]",
170 invo_name);
171 print_help (buf, switches, 1);
172 done (1);
173 case VERSIONSW:
174 print_version(invo_name);
175 done (1);
176
177 case ALLSW:
178 all = 1;
179 continue;
180
181 case NALLSW:
182 all = 0;
183 continue;
184
185 case CREATSW:
186 fcreat = 1;
187 continue;
188 case NCREATSW:
189 fcreat = -1;
190 continue;
191
192 case FASTSW:
193 fshort++;
194 continue;
195 case NFASTSW:
196 fshort = 0;
197 continue;
198
199 case HDRSW:
200 fheader = 1;
201 continue;
202 case NHDRSW:
203 fheader = -1;
204 continue;
205
206 case PACKSW:
207 fpack++;
208 continue;
209 case NPACKSW:
210 fpack = 0;
211 continue;
212
213 case VERBSW:
214 fverb++;
215 continue;
216 case NVERBSW:
217 fverb = 0;
218 continue;
219
220 case RECURSW:
221 frecurse++;
222 continue;
223 case NRECRSW:
224 frecurse = 0;
225 continue;
226
227 case TOTALSW:
228 ftotal = 1;
229 continue;
230 case NTOTLSW:
231 ftotal = -1;
232 continue;
233
234 case PRNTSW:
235 printsw = 1;
236 continue;
237 case NPRNTSW:
238 printsw = 0;
239 continue;
240
241 case LISTSW:
242 listsw = 1;
243 continue;
244 case NLISTSW:
245 listsw = 0;
246 continue;
247
248 case PUSHSW:
249 pushsw = 1;
250 listsw = 1;
251 popsw = 0;
252 continue;
253 case POPSW:
254 popsw = 1;
255 listsw = 1;
256 pushsw = 0;
257 continue;
258 }
259 }
260 if (*cp == '+' || *cp == '@') {
261 if (argfolder)
262 adios (NULL, "only one folder at a time!");
263 else
264 argfolder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
265 } else {
266 if (msg)
267 adios (NULL, "only one (current) message at a time!");
268 else
269 msg = cp;
270 }
271 }
272
273 if (!context_find ("path"))
274 free (path ("./", TFOLDER));
275 nmhdir = concat (m_maildir (""), "/", NULL);
276
277 /*
278 * If we aren't working with the folder stack
279 * (-push, -pop, -list) then the default is to print.
280 */
281 if (pushsw == 0 && popsw == 0 && listsw == 0)
282 printsw++;
283
284 /* Pushing a folder onto the folder stack */
285 if (pushsw) {
286 if (!argfolder) {
287 /* If no folder is given, the current folder and */
288 /* the top of the folder stack are swapped. */
289 if ((cp = context_find (stack))) {
290 dp = getcpy (cp);
291 ap = brkstring (dp, " ", "\n");
292 argfolder = getcpy(*ap++);
293 } else {
294 adios (NULL, "no other folder");
295 }
296 for (cp = getcpy (getfolder (1)); *ap; ap++)
297 cp = add (*ap, add (" ", cp));
298 free (dp);
299 context_replace (stack, cp); /* update folder stack */
300 } else {
301 /* update folder stack */
302 context_replace (stack,
303 (cp = context_find (stack))
304 ? concat (getfolder (1), " ", cp, NULL)
305 : getcpy (getfolder (1)));
306 }
307 }
308
309 /* Popping a folder off of the folder stack */
310 if (popsw) {
311 if (argfolder)
312 adios (NULL, "sorry, no folders allowed with -pop");
313 if ((cp = context_find (stack))) {
314 dp = getcpy (cp);
315 ap = brkstring (dp, " ", "\n");
316 argfolder = getcpy(*ap++);
317 } else {
318 adios (NULL, "folder stack empty");
319 }
320 if (*ap) {
321 /* if there's anything left in the stack */
322 cp = getcpy (*ap++);
323 for (; *ap; ap++)
324 cp = add (*ap, add (" ", cp));
325 context_replace (stack, cp); /* update folder stack */
326 } else {
327 context_del (stack); /* delete folder stack entry from context */
328 }
329 free (dp);
330 }
331 if (pushsw || popsw) {
332 cp = m_maildir(argfolder);
333 if (access (cp, F_OK) == NOTOK)
334 adios (cp, "unable to find folder");
335 context_replace (pfolder, argfolder); /* update current folder */
336 context_save (); /* save the context file */
337 argfolder = NULL;
338 }
339
340 /* Listing the folder stack */
341 if (listsw) {
342 printf ("%s", argfolder ? argfolder : getfolder (1));
343 if ((cp = context_find (stack))) {
344 dp = getcpy (cp);
345 for (ap = brkstring (dp, " ", "\n"); *ap; ap++)
346 printf (" %s", *ap);
347 free (dp);
348 }
349 printf ("\n");
350
351 if (!printsw)
352 done (0);
353 }
354
355 /* Allocate initial space to record folder names */
356 maxfolders = NUMFOLDERS;
357 folds = mh_xmalloc (maxfolders * sizeof(char *));
358
359 /* Allocate initial space to record folder information */
360 maxFolderInfo = NUMFOLDERS;
361 fi = mh_xmalloc (maxFolderInfo * sizeof(*fi));
362
363 /*
364 * Scan the folders
365 */
366 if (all || ftotal > 0) {
367 /*
368 * If no folder is given, do them all
369 */
370 if (!argfolder) {
371 if (msg)
372 admonish (NULL, "no folder given for message %s", msg);
373 readonly_folders (); /* do any readonly folders */
374 strncpy (folder, (cp = context_find (pfolder)) ? cp : "", sizeof(folder));
375 dodir (".");
376 } else {
377 strncpy (folder, argfolder, sizeof(folder));
378 if (get_folder_info (argfolder, msg)) {
379 context_replace (pfolder, argfolder);/* update current folder */
380 context_save (); /* save the context file */
381 }
382 /*
383 * Since recurse wasn't done in get_folder_info(),
384 * we still need to list all level-1 sub-folders.
385 */
386 if (!frecurse)
387 dodir (folder);
388 }
389 } else {
390 strncpy (folder, argfolder ? argfolder : getfolder (1), sizeof(folder));
391
392 /*
393 * Check if folder exists. If not, then see if
394 * we should create it, or just exit.
395 */
396 if (stat (strncpy (buf, m_maildir (folder), sizeof(buf)), &st) == -1) {
397 if (errno != ENOENT)
398 adios (buf, "error on folder");
399 if (fcreat == 0) {
400 /* ask before creating folder */
401 cp = concat ("Create folder \"", buf, "\"? ", NULL);
402 if (!getanswer (cp))
403 done (1);
404 free (cp);
405 } else if (fcreat == -1) {
406 /* do not create, so exit */
407 done (1);
408 }
409 if (!makedir (buf))
410 adios (NULL, "unable to create folder %s", buf);
411 }
412
413 if (get_folder_info (folder, msg) && argfolder) {
414 /* update current folder */
415 context_replace (pfolder, argfolder);
416 }
417 }
418
419 /*
420 * Print out folder information
421 */
422 print_folders();
423
424 context_save (); /* save the context file */
425 return done (0);
426 }
427
428 /*
429 * Base routine for scanning a folder
430 */
431
432 static void
433 dodir (char *dir)
434 {
435 int i;
436 int os = start;
437 int of = foldp;
438 char buffer[BUFSIZ];
439
440 start = foldp;
441
442 /* change directory to base of nmh directory */
443 if (chdir (nmhdir) == NOTOK)
444 adios (nmhdir, "unable to change directory to");
445
446 addir (strncpy (buffer, dir, sizeof(buffer)));
447
448 for (i = start; i < foldp; i++) {
449 get_folder_info (folds[i], NULL);
450 fflush (stdout);
451 }
452
453 start = os;
454 foldp = of;
455 }
456
457 static int
458 get_folder_info (char *fold, char *msg)
459 {
460 int i, retval = 1;
461 char *mailfile;
462 struct msgs *mp = NULL;
463
464 i = total_folders++;
465
466 /*
467 * if necessary, reallocate the space
468 * for folder information
469 */
470 if (total_folders >= maxFolderInfo) {
471 maxFolderInfo += NUMFOLDERS;
472 fi = mh_xrealloc (fi, maxFolderInfo * sizeof(*fi));
473 }
474
475 fi[i].name = fold;
476 fi[i].nummsg = 0;
477 fi[i].curmsg = 0;
478 fi[i].lowmsg = 0;
479 fi[i].hghmsg = 0;
480 fi[i].others = 0;
481 fi[i].error = 0;
482
483 mailfile = m_maildir (fold);
484
485 if (!chdir (mailfile)) {
486 if ((ftotal > 0) || !fshort || msg || fpack) {
487 /*
488 * create message structure and get folder info
489 */
490 if (!(mp = folder_read (fold))) {
491 admonish (NULL, "unable to read folder %s", fold);
492 return 0;
493 }
494
495 /* set the current message */
496 if (msg && !sfold (mp, msg))
497 retval = 0;
498
499 if (fpack) {
500 if (folder_pack (&mp, fverb) == -1)
501 done (1);
502 seq_save (mp); /* synchronize the sequences */
503 context_save (); /* save the context file */
504 }
505
506 /* record info for this folder */
507 if ((ftotal > 0) || !fshort) {
508 fi[i].nummsg = mp->nummsg;
509 fi[i].curmsg = mp->curmsg;
510 fi[i].lowmsg = mp->lowmsg;
511 fi[i].hghmsg = mp->hghmsg;
512 fi[i].others = other_files (mp);
513 }
514
515 folder_free (mp); /* free folder/message structure */
516 }
517 } else {
518 fi[i].error = 1;
519 }
520
521 if (frecurse && (fshort || fi[i].others) && (fi[i].error == 0))
522 dodir (fold);
523 return retval;
524 }
525
526 /*
527 * Print folder information
528 */
529
530 static void
531 print_folders (void)
532 {
533 int i, len, hasempty = 0, curprinted;
534 int maxlen = 0, maxnummsg = 0, maxlowmsg = 0;
535 int maxhghmsg = 0, maxcurmsg = 0, total_msgs = 0;
536 int nummsgdigits, lowmsgdigits;
537 int hghmsgdigits, curmsgdigits;
538 char tmpname[BUFSIZ];
539
540 /*
541 * compute a few values needed to for
542 * printing various fields
543 */
544 for (i = 0; i < total_folders; i++) {
545 /* length of folder name */
546 len = strlen (fi[i].name);
547 if (len > maxlen)
548 maxlen = len;
549
550 /* If folder has error, skip the rest */
551 if (fi[i].error)
552 continue;
553
554 /* calculate total number of messages */
555 total_msgs += fi[i].nummsg;
556
557 /* maximum number of messages */
558 if (fi[i].nummsg > maxnummsg)
559 maxnummsg = fi[i].nummsg;
560
561 /* maximum low message */
562 if (fi[i].lowmsg > maxlowmsg)
563 maxlowmsg = fi[i].lowmsg;
564
565 /* maximum high message */
566 if (fi[i].hghmsg > maxhghmsg)
567 maxhghmsg = fi[i].hghmsg;
568
569 /* maximum current message */
570 if (fi[i].curmsg >= fi[i].lowmsg &&
571 fi[i].curmsg <= fi[i].hghmsg &&
572 fi[i].curmsg > maxcurmsg)
573 maxcurmsg = fi[i].curmsg;
574
575 /* check for empty folders */
576 if (fi[i].nummsg == 0)
577 hasempty = 1;
578 }
579 nummsgdigits = num_digits (maxnummsg);
580 lowmsgdigits = num_digits (maxlowmsg);
581 hghmsgdigits = num_digits (maxhghmsg);
582 curmsgdigits = num_digits (maxcurmsg);
583
584 if (hasempty && nummsgdigits < 2)
585 nummsgdigits = 2;
586
587 /*
588 * Print the header
589 */
590 if (fheader > 0 || (all && !fshort && fheader >= 0))
591 printf ("%-*s %*s %-*s; %-*s %*s\n",
592 maxlen+1, "FOLDER",
593 nummsgdigits + 13, "# MESSAGES",
594 lowmsgdigits + hghmsgdigits + 4, " RANGE",
595 curmsgdigits + 4, "CUR",
596 9, "(OTHERS)");
597
598 /*
599 * Print folder information
600 */
601 if (all || fshort || ftotal < 1) {
602 for (i = 0; i < total_folders; i++) {
603 if (fshort) {
604 printf ("%s\n", fi[i].name);
605 continue;
606 }
607
608 /* Add `+' to end of name, if folder is current */
609 if (strcmp (folder, fi[i].name))
610 snprintf (tmpname, sizeof(tmpname), "%s", fi[i].name);
611 else
612 snprintf (tmpname, sizeof(tmpname), "%s+", fi[i].name);
613
614 if (fi[i].error) {
615 printf ("%-*s is unreadable\n", maxlen+1, tmpname);
616 continue;
617 }
618
619 printf ("%-*s ", maxlen+1, tmpname);
620
621 curprinted = 0; /* remember if we print cur */
622 if (fi[i].nummsg == 0) {
623 printf ("has %*s messages%*s",
624 nummsgdigits, "no",
625 fi[i].others ? lowmsgdigits + hghmsgdigits + 5 : 0, "");
626 } else {
627 printf ("has %*d message%s (%*d-%*d)",
628 nummsgdigits, fi[i].nummsg,
629 (fi[i].nummsg == 1) ? " " : "s",
630 lowmsgdigits, fi[i].lowmsg,
631 hghmsgdigits, fi[i].hghmsg);
632 if (fi[i].curmsg >= fi[i].lowmsg && fi[i].curmsg <= fi[i].hghmsg) {
633 curprinted = 1;
634 printf ("; cur=%*d", curmsgdigits, fi[i].curmsg);
635 }
636 }
637
638 if (fi[i].others)
639 printf (";%*s (others)", curprinted ? 0 : curmsgdigits + 6, "");
640 printf (".\n");
641 }
642 }
643
644 /*
645 * Print folder/message totals
646 */
647 if (ftotal > 0 || (all && !fshort && ftotal >= 0)) {
648 if (all)
649 printf ("\n");
650 printf ("TOTAL = %d message%c in %d folder%s.\n",
651 total_msgs, total_msgs != 1 ? 's' : ' ',
652 total_folders, total_folders != 1 ? "s" : "");
653 }
654
655 fflush (stdout);
656 }
657
658 /*
659 * Calculate the number of digits in a nonnegative integer
660 */
661 int
662 num_digits (int n)
663 {
664 int ndigits = 0;
665
666 /* Sanity check */
667 if (n < 0)
668 adios (NULL, "oops, num_digits called with negative value");
669
670 if (n == 0)
671 return 1;
672
673 while (n) {
674 n /= 10;
675 ndigits++;
676 }
677
678 return ndigits;
679 }
680
681 /*
682 * Set the current message and sychronize sequences
683 */
684
685 static int
686 sfold (struct msgs *mp, char *msg)
687 {
688 /* parse the message range/sequence/name and set SELECTED */
689 if (!m_convert (mp, msg))
690 return 0;
691
692 if (mp->numsel > 1) {
693 admonish (NULL, "only one message at a time!");
694 return 0;
695 }
696 seq_setprev (mp); /* set the previous-sequence */
697 seq_setcur (mp, mp->lowsel);/* set current message */
698 seq_save (mp); /* synchronize message sequences */
699 context_save (); /* save the context file */
700
701 return 1;
702 }
703
704
705 static void
706 addir (char *name)
707 {
708 int nlink;
709 char *base, *cp;
710 struct stat st;
711 struct dirent *dp;
712 DIR * dd;
713
714 cp = name + strlen (name);
715 *cp++ = '/';
716 *cp = '\0';
717
718 /*
719 * A hack to skip over a leading
720 * "./" in folder names.
721 */
722 base = strcmp (name, "./") ? name : name + 2;
723
724 /* short-cut to see if directory has any sub-directories */
725 if (stat (name, &st) != -1 && st.st_nlink == 2)
726 return;
727
728 if (!(dd = opendir (name))) {
729 admonish (name, "unable to read directory ");
730 return;
731 }
732
733 /*
734 * Keep track of the number of directories we've seen
735 * so we can quit stat'ing early, if we've seen them all.
736 */
737 nlink = st.st_nlink;
738
739 while (nlink && (dp = readdir (dd))) {
740 if (!strcmp (dp->d_name, ".") || !strcmp (dp->d_name, "..")) {
741 nlink--;
742 continue;
743 }
744 if (cp + NLENGTH(dp) + 2 >= name + BUFSIZ)
745 continue;
746 strcpy (cp, dp->d_name);
747 if (stat (name, &st) != -1 && S_ISDIR(st.st_mode)) {
748 /*
749 * Check if this was really a symbolic link pointing at
750 * a directory. If not, then decrement link count.
751 */
752 if (lstat (name, &st) == -1)
753 nlink--;
754 addfold (base);
755 }
756 }
757
758 closedir (dd);
759 *--cp = '\0';
760 }
761
762 /*
763 * Add the folder name into the
764 * list in a sorted fashion.
765 */
766
767 static void
768 addfold (char *fold)
769 {
770 register int i, j;
771 register char *cp;
772
773 /* if necessary, reallocate the space for folder names */
774 if (foldp >= maxfolders) {
775 maxfolders += NUMFOLDERS;
776 folds = mh_xrealloc (folds, maxfolders * sizeof(char *));
777 }
778
779 cp = getcpy (fold);
780 for (i = start; i < foldp; i++)
781 if (compare (cp, folds[i]) < 0) {
782 for (j = foldp - 1; j >= i; j--)
783 folds[j + 1] = folds[j];
784 foldp++;
785 folds[i] = cp;
786 return;
787 }
788
789 folds[foldp++] = cp;
790 }
791
792
793 static int
794 compare (char *s1, char *s2)
795 {
796 register int i;
797
798 while (*s1 || *s2)
799 if ((i = *s1++ - *s2++))
800 return i;
801
802 return 0;
803 }
804
805 /*
806 * Do the read only folders
807 */
808
809 static void
810 readonly_folders (void)
811 {
812 int atrlen;
813 char atrcur[BUFSIZ];
814 register struct node *np;
815
816 snprintf (atrcur, sizeof(atrcur), "atr-%s-", current);
817 atrlen = strlen (atrcur);
818
819 for (np = m_defs; np; np = np->n_next)
820 if (ssequal (atrcur, np->n_name)
821 && !ssequal (nmhdir, np->n_name + atrlen))
822 get_folder_info (np->n_name + atrlen, NULL);
823 }