]> diplodocus.org Git - nmh/blob - uip/folder.c
Changed from returning void to returning int so that main()s who call done() at
[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
10 #include <h/mh.h>
11 #include <errno.h>
12
13 static struct swit switches[] = {
14 #define ALLSW 0
15 { "all", 0 },
16 #define NALLSW 1
17 { "noall", 0 },
18 #define CREATSW 2
19 { "create", 0 },
20 #define NCREATSW 3
21 { "nocreate", 0 },
22 #define FASTSW 4
23 { "fast", 0 },
24 #define NFASTSW 5
25 { "nofast", 0 },
26 #define HDRSW 6
27 { "header", 0 },
28 #define NHDRSW 7
29 { "noheader", 0 },
30 #define PACKSW 8
31 { "pack", 0 },
32 #define NPACKSW 9
33 { "nopack", 0 },
34 #define VERBSW 10
35 { "verbose", 0 },
36 #define NVERBSW 11
37 { "noverbose", 0 },
38 #define RECURSW 12
39 { "recurse", 0 },
40 #define NRECRSW 13
41 { "norecurse", 0 },
42 #define TOTALSW 14
43 { "total", 0 },
44 #define NTOTLSW 15
45 { "nototal", 0 },
46 #define LISTSW 16
47 { "list", 0 },
48 #define NLISTSW 17
49 { "nolist", 0 },
50 #define PRNTSW 18
51 { "print", 0 },
52 #define NPRNTSW 19
53 { "noprint", -4 },
54 #define PUSHSW 20
55 { "push", 0 },
56 #define POPSW 21
57 { "pop", 0 },
58 #define VERSIONSW 22
59 { "version", 0 },
60 #define HELPSW 23
61 { "help", 4 },
62 { NULL, 0 }
63 };
64
65 extern int errno;
66
67 static int fshort = 0; /* output only folder names */
68 static int fcreat = 0; /* should we ask to create new folders? */
69 static int fpack = 0; /* are we packing the folder? */
70 static int fverb = 0; /* print actions taken while packing folder */
71 static int fheader = 0; /* should we output a header? */
72 static int frecurse = 0; /* recurse through subfolders */
73 static int ftotal = 0; /* should we output the totals? */
74 static int all = 0; /* should we output all folders */
75
76 static int total_folders = 0; /* total number of folders */
77
78 static int start = 0;
79 static int foldp = 0;
80
81 static char *nmhdir;
82 static char *stack = "Folder-Stack";
83 static char folder[BUFSIZ];
84
85 #define NUMFOLDERS 100
86
87 /*
88 * This is how many folders we currently can hold in the array
89 * `folds'. We increase this amount by NUMFOLDERS at a time.
90 */
91 static int maxfolders;
92 static char **folds;
93
94 /*
95 * Structure to hold information about
96 * folders as we scan them.
97 */
98 struct FolderInfo {
99 char *name;
100 int nummsg;
101 int curmsg;
102 int lowmsg;
103 int hghmsg;
104 int others; /* others == 1 if other files in folder */
105 int error; /* error == 1 for unreadable folder */
106 };
107
108 /*
109 * Dynamically allocated space to hold
110 * all the folder information.
111 */
112 static struct FolderInfo *fi;
113 static int maxFolderInfo;
114
115 /*
116 * static prototypes
117 */
118 static void dodir (char *);
119 static int get_folder_info (char *, char *);
120 static void print_folders (void);
121 static int num_digits (int);
122 static int sfold (struct msgs *, char *);
123 static void addir (char *);
124 static void addfold (char *);
125 static int compare (char *, char *);
126 static void readonly_folders (void);
127
128
129 int
130 main (int argc, char **argv)
131 {
132 int printsw = 0, listsw = 0;
133 int pushsw = 0, popsw = 0;
134 char *cp, *dp, *msg = NULL, *argfolder = NULL;
135 char **ap, **argp, buf[BUFSIZ], **arguments;
136 struct stat st;
137
138 #ifdef LOCALE
139 setlocale(LC_ALL, "");
140 #endif
141 invo_name = r1bindex (argv[0], '/');
142
143 /* read user profile/context */
144 context_read();
145
146 /*
147 * If program was invoked with name ending
148 * in `s', then add switch `-all'.
149 */
150 if (argv[0][strlen (argv[0]) - 1] == 's')
151 all = 1;
152
153 arguments = getarguments (invo_name, argc, argv, 1);
154 argp = arguments;
155
156 while ((cp = *argp++)) {
157 if (*cp == '-') {
158 switch (smatch (++cp, switches)) {
159 case AMBIGSW:
160 ambigsw (cp, switches);
161 done (1);
162 case UNKWNSW:
163 adios (NULL, "-%s unknown", cp);
164
165 case HELPSW:
166 snprintf (buf, sizeof(buf), "%s [+folder] [msg] [switches]",
167 invo_name);
168 print_help (buf, switches, 1);
169 done (1);
170 case VERSIONSW:
171 print_version(invo_name);
172 done (1);
173
174 case ALLSW:
175 all = 1;
176 continue;
177
178 case NALLSW:
179 all = 0;
180 continue;
181
182 case CREATSW:
183 fcreat = 1;
184 continue;
185 case NCREATSW:
186 fcreat = -1;
187 continue;
188
189 case FASTSW:
190 fshort++;
191 continue;
192 case NFASTSW:
193 fshort = 0;
194 continue;
195
196 case HDRSW:
197 fheader = 1;
198 continue;
199 case NHDRSW:
200 fheader = -1;
201 continue;
202
203 case PACKSW:
204 fpack++;
205 continue;
206 case NPACKSW:
207 fpack = 0;
208 continue;
209
210 case VERBSW:
211 fverb++;
212 continue;
213 case NVERBSW:
214 fverb = 0;
215 continue;
216
217 case RECURSW:
218 frecurse++;
219 continue;
220 case NRECRSW:
221 frecurse = 0;
222 continue;
223
224 case TOTALSW:
225 ftotal = 1;
226 continue;
227 case NTOTLSW:
228 ftotal = -1;
229 continue;
230
231 case PRNTSW:
232 printsw = 1;
233 continue;
234 case NPRNTSW:
235 printsw = 0;
236 continue;
237
238 case LISTSW:
239 listsw = 1;
240 continue;
241 case NLISTSW:
242 listsw = 0;
243 continue;
244
245 case PUSHSW:
246 pushsw = 1;
247 listsw = 1;
248 popsw = 0;
249 continue;
250 case POPSW:
251 popsw = 1;
252 listsw = 1;
253 pushsw = 0;
254 continue;
255 }
256 }
257 if (*cp == '+' || *cp == '@') {
258 if (argfolder)
259 adios (NULL, "only one folder at a time!");
260 else
261 argfolder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
262 } else {
263 if (msg)
264 adios (NULL, "only one (current) message at a time!");
265 else
266 msg = cp;
267 }
268 }
269
270 if (!context_find ("path"))
271 free (path ("./", TFOLDER));
272 nmhdir = concat (m_maildir (""), "/", NULL);
273
274 /*
275 * If we aren't working with the folder stack
276 * (-push, -pop, -list) then the default is to print.
277 */
278 if (pushsw == 0 && popsw == 0 && listsw == 0)
279 printsw++;
280
281 /* Pushing a folder onto the folder stack */
282 if (pushsw) {
283 if (!argfolder) {
284 /* If no folder is given, the current folder and */
285 /* the top of the folder stack are swapped. */
286 if ((cp = context_find (stack))) {
287 dp = getcpy (cp);
288 ap = brkstring (dp, " ", "\n");
289 argfolder = getcpy(*ap++);
290 } else {
291 adios (NULL, "no other folder");
292 }
293 for (cp = getcpy (getfolder (1)); *ap; ap++)
294 cp = add (*ap, add (" ", cp));
295 free (dp);
296 context_replace (stack, cp); /* update folder stack */
297 } else {
298 /* update folder stack */
299 context_replace (stack,
300 (cp = context_find (stack))
301 ? concat (getfolder (1), " ", cp, NULL)
302 : getcpy (getfolder (1)));
303 }
304 }
305
306 /* Popping a folder off of the folder stack */
307 if (popsw) {
308 if (argfolder)
309 adios (NULL, "sorry, no folders allowed with -pop");
310 if ((cp = context_find (stack))) {
311 dp = getcpy (cp);
312 ap = brkstring (dp, " ", "\n");
313 argfolder = getcpy(*ap++);
314 } else {
315 adios (NULL, "folder stack empty");
316 }
317 if (*ap) {
318 /* if there's anything left in the stack */
319 cp = getcpy (*ap++);
320 for (; *ap; ap++)
321 cp = add (*ap, add (" ", cp));
322 context_replace (stack, cp); /* update folder stack */
323 } else {
324 context_del (stack); /* delete folder stack entry from context */
325 }
326 free (dp);
327 }
328 if (pushsw || popsw) {
329 cp = m_maildir(argfolder);
330 if (access (cp, F_OK) == NOTOK)
331 adios (cp, "unable to find folder");
332 context_replace (pfolder, argfolder); /* update current folder */
333 context_save (); /* save the context file */
334 argfolder = NULL;
335 }
336
337 /* Listing the folder stack */
338 if (listsw) {
339 printf ("%s", argfolder ? argfolder : getfolder (1));
340 if ((cp = context_find (stack))) {
341 dp = getcpy (cp);
342 for (ap = brkstring (dp, " ", "\n"); *ap; ap++)
343 printf (" %s", *ap);
344 free (dp);
345 }
346 printf ("\n");
347
348 if (!printsw)
349 done (0);
350 }
351
352 /* Allocate initial space to record folder names */
353 maxfolders = NUMFOLDERS;
354 if ((folds = malloc (maxfolders * sizeof(char *))) == NULL)
355 adios (NULL, "unable to allocate storage for folder names");
356
357 /* Allocate initial space to record folder information */
358 maxFolderInfo = NUMFOLDERS;
359 if ((fi = malloc (maxFolderInfo * sizeof(*fi))) == NULL)
360 adios (NULL, "unable to allocate storage for folder info");
361
362 /*
363 * Scan the folders
364 */
365 if (all || ftotal > 0) {
366 /*
367 * If no folder is given, do them all
368 */
369 if (!argfolder) {
370 if (msg)
371 admonish (NULL, "no folder given for message %s", msg);
372 readonly_folders (); /* do any readonly folders */
373 strncpy (folder, (cp = context_find (pfolder)) ? cp : "", sizeof(folder));
374 dodir (".");
375 } else {
376 strncpy (folder, argfolder, sizeof(folder));
377 if (get_folder_info (argfolder, msg)) {
378 context_replace (pfolder, argfolder);/* update current folder */
379 context_save (); /* save the context file */
380 }
381 /*
382 * Since recurse wasn't done in get_folder_info(),
383 * we still need to list all level-1 sub-folders.
384 */
385 if (!frecurse)
386 dodir (folder);
387 }
388 } else {
389 strncpy (folder, argfolder ? argfolder : getfolder (1), sizeof(folder));
390
391 /*
392 * Check if folder exists. If not, then see if
393 * we should create it, or just exit.
394 */
395 if (stat (strncpy (buf, m_maildir (folder), sizeof(buf)), &st) == -1) {
396 if (errno != ENOENT)
397 adios (buf, "error on folder");
398 if (fcreat == 0) {
399 /* ask before creating folder */
400 cp = concat ("Create folder \"", buf, "\"? ", NULL);
401 if (!getanswer (cp))
402 done (1);
403 free (cp);
404 } else if (fcreat == -1) {
405 /* do not create, so exit */
406 done (1);
407 }
408 if (!makedir (buf))
409 adios (NULL, "unable to create folder %s", buf);
410 }
411
412 if (get_folder_info (folder, msg) && argfolder) {
413 /* update current folder */
414 context_replace (pfolder, argfolder);
415 }
416 }
417
418 /*
419 * Print out folder information
420 */
421 print_folders();
422
423 context_save (); /* save the context file */
424 done (0);
425 }
426
427 /*
428 * Base routine for scanning a folder
429 */
430
431 static void
432 dodir (char *dir)
433 {
434 int i;
435 int os = start;
436 int of = foldp;
437 char buffer[BUFSIZ];
438
439 start = foldp;
440
441 /* change directory to base of nmh directory */
442 if (chdir (nmhdir) == NOTOK)
443 adios (nmhdir, "unable to change directory to");
444
445 addir (strncpy (buffer, dir, sizeof(buffer)));
446
447 for (i = start; i < foldp; i++) {
448 get_folder_info (folds[i], NULL);
449 fflush (stdout);
450 }
451
452 start = os;
453 foldp = of;
454 }
455
456 static int
457 get_folder_info (char *fold, char *msg)
458 {
459 int i, retval = 1;
460 char *mailfile;
461 struct msgs *mp = NULL;
462
463 i = total_folders++;
464
465 /*
466 * if necessary, reallocate the space
467 * for folder information
468 */
469 if (total_folders >= maxFolderInfo) {
470 maxFolderInfo += NUMFOLDERS;
471 if ((fi = realloc (fi, maxFolderInfo * sizeof(*fi))) == NULL)
472 adios (NULL, "unable to re-allocate storage for folder info");
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 if ((folds = realloc (folds, maxfolders * sizeof(char *))) == NULL)
777 adios (NULL, "unable to re-allocate storage for folder names");
778 }
779
780 cp = getcpy (fold);
781 for (i = start; i < foldp; i++)
782 if (compare (cp, folds[i]) < 0) {
783 for (j = foldp - 1; j >= i; j--)
784 folds[j + 1] = folds[j];
785 foldp++;
786 folds[i] = cp;
787 return;
788 }
789
790 folds[foldp++] = cp;
791 }
792
793
794 static int
795 compare (char *s1, char *s2)
796 {
797 register int i;
798
799 while (*s1 || *s2)
800 if ((i = *s1++ - *s2++))
801 return i;
802
803 return 0;
804 }
805
806 /*
807 * Do the read only folders
808 */
809
810 static void
811 readonly_folders (void)
812 {
813 int atrlen;
814 char atrcur[BUFSIZ];
815 register struct node *np;
816
817 /* sanity check - check that context has been read */
818 if (defpath == NULL)
819 adios (NULL, "oops, context hasn't been read yet");
820
821 snprintf (atrcur, sizeof(atrcur), "atr-%s-", current);
822 atrlen = strlen (atrcur);
823
824 for (np = m_defs; np; np = np->n_next)
825 if (ssequal (atrcur, np->n_name)
826 && !ssequal (nmhdir, np->n_name + atrlen))
827 get_folder_info (np->n_name + atrlen, NULL);
828 }