f940316918b07c1cdda06ab080869864acbaf852
[mmh] / 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     return 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 }