Removed configure flag --disable-locale and have it always enabled.
[mmh] / uip / folder.c
1 /*
2 ** folder(s).c -- set/list the current message and/or folder
3 **             -- push/pop a folder onto/from the folder stack
4 **             -- list the folder stack
5 **
6 ** This code is Copyright (c) 2002, 2008, by the authors of nmh.  See the
7 ** COPYRIGHT file in the root directory of the nmh distribution for
8 ** complete copyright information.
9 */
10
11 #include <h/mh.h>
12 #include <h/crawl_folders.h>
13 #include <h/utils.h>
14 #include <errno.h>
15
16 static struct swit switches[] = {
17 #define ALLSW  0
18         { "all", 0 },
19 #define NALLSW  1
20         { "noall", 2 },
21 #define CREATSW  2
22         { "create", 0 },
23 #define NCREATSW  3
24         { "nocreate", 2 },
25 #define FASTSW  4
26         { "fast", 0 },
27 #define NFASTSW  5
28         { "nofast", 2 },
29 #define PACKSW  6
30         { "pack", 0 },
31 #define NPACKSW  7
32         { "nopack", 2 },
33 #define VERBSW  8
34         { "verbose", 0 },
35 #define NVERBSW  9
36         { "noverbose", 2 },
37 #define RECURSW  10
38         { "recurse", 0 },
39 #define NRECRSW  11
40         { "norecurse", 2 },
41 #define TOTALSW  12
42         { "total", 0 },
43 #define NTOTLSW  13
44         { "nototal", 2 },
45 #define LISTSW  14
46         { "list", 0 },
47 #define NLISTSW  15
48         { "nolist", 2 },
49 #define PRNTSW  16
50         { "print", 0 },
51 #define NPRNTSW  17
52         { "noprint", -4 },
53 #define PUSHSW  18
54         { "push", 0 },
55 #define POPSW  19
56         { "pop", 0 },
57 #define VERSIONSW  20
58         { "Version", 0 },
59 #define HELPSW  21
60         { "help", 0 },
61         { NULL, 0 }
62 };
63
64 static int fshort   = 0;  /* output only folder names */
65 static int fcreat   = 0;  /* should we ask to create new folders? */
66 static int fpack    = 0;  /* are we packing the folder? */
67 static int fverb    = 0;  /* print actions taken while packing folder */
68 static int frecurse = 0;  /* recurse through subfolders */
69 static int ftotal   = 0;  /* should we output the totals? */
70 static int all      = 0;  /* should we output all folders */
71
72 static int total_folders = 0;  /* total number of folders */
73
74 static char *nmhdir;
75 static char *stack = "Folder-Stack";
76 static char folder[BUFSIZ];
77
78 /*
79 ** Structure to hold information about
80 ** folders as we scan them.
81 */
82 struct FolderInfo {
83         char *name;
84         int nummsg;
85         int curmsg;
86         int lowmsg;
87         int hghmsg;
88         int others;  /* others == 1 if other files in folder */
89         int error;  /* error == 1 for unreadable folder */
90 };
91
92 /*
93 ** Dynamically allocated space to hold
94 ** all the folder information.
95 */
96 static struct FolderInfo *fi;
97 static int maxFolderInfo;
98
99 /*
100 ** static prototypes
101 */
102 static int get_folder_info(char *, char *);
103 static crawl_callback_t get_folder_info_callback;
104 static void print_folders(void);
105 static int sfold(struct msgs *, char *);
106 static void readonly_folders(void);
107 static int folder_pack(struct msgs **, int);
108
109
110 int
111 main(int argc, char **argv)
112 {
113         int printsw = 0, listsw = 0;
114         int pushsw = 0, popsw = 0;
115         char *cp, *dp, *msg = NULL, *argfolder = NULL;
116         char **ap, **argp, buf[BUFSIZ], **arguments;
117
118         setlocale(LC_ALL, "");
119         invo_name = mhbasename(argv[0]);
120
121         /* read user profile/context */
122         context_read();
123
124         /*
125         ** If program was invoked with name ending
126         ** in `s', then add switch `-all'.
127         */
128         if (argv[0][strlen(argv[0]) - 1] == 's')
129                 all = 1;
130
131         arguments = getarguments(invo_name, argc, argv, 1);
132         argp = arguments;
133
134         while ((cp = *argp++)) {
135                 if (*cp == '-') {
136                         switch (smatch(++cp, switches)) {
137                         case AMBIGSW:
138                                 ambigsw(cp, switches);
139                                 done(1);
140                         case UNKWNSW:
141                                 adios(NULL, "-%s unknown", cp);
142
143                         case HELPSW:
144                                 snprintf(buf, sizeof(buf), "%s [+folder] [msg] [switches]", invo_name);
145                                 print_help(buf, switches, 1);
146                                 done(1);
147                         case VERSIONSW:
148                                 print_version(invo_name);
149                                 done(1);
150
151                         case ALLSW:
152                                 all = 1;
153                                 continue;
154
155                         case NALLSW:
156                                 all = 0;
157                                 continue;
158
159                         case CREATSW:
160                                 fcreat = 1;
161                                 continue;
162                         case NCREATSW:
163                                 fcreat = -1;
164                                 continue;
165
166                         case FASTSW:
167                                 fshort++;
168                                 continue;
169                         case NFASTSW:
170                                 fshort = 0;
171                                 continue;
172
173                         case PACKSW:
174                                 fpack++;
175                                 continue;
176                         case NPACKSW:
177                                 fpack = 0;
178                                 continue;
179
180                         case VERBSW:
181                                 fverb++;
182                                 continue;
183                         case NVERBSW:
184                                 fverb = 0;
185                                 continue;
186
187                         case RECURSW:
188                                 frecurse++;
189                                 continue;
190                         case NRECRSW:
191                                 frecurse = 0;
192                                 continue;
193
194                         case TOTALSW:
195                                 ftotal = 1;
196                                 continue;
197                         case NTOTLSW:
198                                 ftotal = -1;
199                                 continue;
200
201                         case PRNTSW:
202                                 printsw = 1;
203                                 continue;
204                         case NPRNTSW:
205                                 printsw = 0;
206                                 continue;
207
208                         case LISTSW:
209                                 listsw = 1;
210                                 continue;
211                         case NLISTSW:
212                                 listsw = 0;
213                                 continue;
214
215                         case PUSHSW:
216                                 pushsw = 1;
217                                 listsw = 1;
218                                 popsw  = 0;
219                                 continue;
220                         case POPSW:
221                                 popsw  = 1;
222                                 listsw = 1;
223                                 pushsw = 0;
224                                 continue;
225                         }
226                 }
227                 if (*cp == '+' || *cp == '@') {
228                         if (argfolder)
229                                 adios(NULL, "only one folder at a time!");
230                         else
231                                 argfolder = getcpy(expandfol(cp));
232                 } else {
233                         if (msg)
234                                 adios(NULL, "only one (current) message at a time!");
235                         else
236                                 msg = cp;
237                 }
238         }
239
240         nmhdir = concat(toabsdir("+"), "/", NULL);
241
242         /*
243         ** If we aren't working with the folder stack
244         ** (-push, -pop, -list) then the default is to print.
245         */
246         if (pushsw == 0 && popsw == 0 && listsw == 0)
247                 printsw++;
248
249         /* Pushing a folder onto the folder stack */
250         if (pushsw) {
251                 if (!argfolder) {
252                         /* If no folder is given, the current folder and */
253                         /* the top of the folder stack are swapped. */
254                         if ((cp = context_find(stack))) {
255                                 dp = getcpy(cp);
256                                 ap = brkstring(dp, " ", "\n");
257                                 argfolder = getcpy(*ap++);
258                         } else {
259                                 adios(NULL, "no other folder");
260                         }
261                         for (cp = getcpy(getcurfol()); *ap; ap++)
262                                 cp = add(*ap, add(" ", cp));
263                         free(dp);
264                         context_replace(stack, cp);  /* update folder stack */
265                 } else {
266                         /* update folder stack */
267                         context_replace(stack, (cp = context_find (stack)) ?
268                                         concat(getcurfol(), " ", cp, NULL) :
269                                         getcpy(getcurfol()));
270                 }
271         }
272
273         /* Popping a folder off of the folder stack */
274         if (popsw) {
275                 if (argfolder)
276                         adios(NULL, "sorry, no folders allowed with -pop");
277                 if ((cp = context_find(stack))) {
278                         dp = getcpy(cp);
279                         ap = brkstring(dp, " ", "\n");
280                         argfolder = getcpy(*ap++);
281                 } else {
282                         adios(NULL, "folder stack empty");
283                 }
284                 if (*ap) {
285                         /* if there's anything left in the stack */
286                         cp = getcpy(*ap++);
287                         for (; *ap; ap++)
288                                 cp = add(*ap, add(" ", cp));
289                         context_replace(stack, cp);  /* update folder stack */
290                 } else {
291                         /* delete folder stack entry from context */
292                         context_del(stack);
293                 }
294                 free(dp);
295         }
296         if (pushsw || popsw) {
297                 cp = toabsdir(argfolder);
298                 if (access(cp, F_OK) == NOTOK)
299                         adios(cp, "unable to find folder");
300                 /* update current folder   */
301                 context_replace(curfolder, argfolder);
302                 context_save();  /* save the context file   */
303                 argfolder = NULL;
304         }
305
306         /* Listing the folder stack */
307         if (listsw) {
308                 printf("%s", argfolder ? argfolder : getcurfol());
309                 if ((cp = context_find(stack))) {
310                         dp = getcpy(cp);
311                         for (ap = brkstring(dp, " ", "\n"); *ap; ap++)
312                                 printf(" %s", *ap);
313                         free(dp);
314                 }
315                 printf("\n");
316
317                 if (!printsw)
318                         done(0);
319         }
320
321         /* Allocate initial space to record folder information */
322         maxFolderInfo = CRAWL_NUMFOLDERS;
323         fi = mh_xmalloc(maxFolderInfo * sizeof(*fi));
324
325         /*
326         ** Scan the folders
327         */
328         if (all || ftotal > 0) {
329                 /*
330                 ** If no folder is given, do them all
331                 */
332                 /*
333                 ** change directory to base of nmh directory for
334                 ** crawl_folders
335                 */
336                 if (chdir(nmhdir) == NOTOK)
337                         adios(nmhdir, "unable to change directory to");
338                 if (!argfolder) {
339                         if (msg)
340                                 admonish(NULL, "no folder given for message %s", msg);
341                         readonly_folders(); /* do any readonly folders */
342                         strncpy(folder, (cp = context_find(curfolder)) ?
343                                         cp : "", sizeof(folder));
344                         crawl_folders(".", get_folder_info_callback, NULL);
345                 } else {
346                         strncpy(folder, argfolder, sizeof(folder));
347                         if (get_folder_info(argfolder, msg)) {
348                                 /* update current folder */
349                                 context_replace(curfolder, argfolder);
350                                 context_save();
351                         }
352                         /*
353                         ** Since recurse wasn't done in get_folder_info(),
354                         ** we still need to list all level-1 sub-folders.
355                         */
356                         if (!frecurse)
357                                 crawl_folders(folder, get_folder_info_callback,
358                                                 NULL);
359                 }
360         } else {
361                 strncpy(folder, argfolder ? argfolder : getcurfol(),
362                                 sizeof(folder));
363
364                 /*
365                 ** Check if folder exists.  If not, then see if
366                 ** we should create it, or just exit.
367                 */
368                 create_folder(toabsdir(folder), fcreat, done);
369
370                 if (get_folder_info(folder, msg) && argfolder) {
371                         /* update current folder */
372                         context_replace(curfolder, argfolder);
373                         }
374         }
375
376         /*
377         ** Print out folder information
378         */
379         print_folders();
380
381         context_save();
382         done(0);
383         return 1;
384 }
385
386 static int
387 get_folder_info_body(char *fold, char *msg, boolean *crawl_children)
388 {
389         int i, retval = 1;
390         struct msgs *mp = NULL;
391
392         i = total_folders++;
393
394         /*
395         ** if necessary, reallocate the space
396         ** for folder information
397         */
398         if (total_folders >= maxFolderInfo) {
399                 maxFolderInfo += CRAWL_NUMFOLDERS;
400                 fi = mh_xrealloc(fi, maxFolderInfo * sizeof(*fi));
401         }
402
403         fi[i].name   = fold;
404         fi[i].nummsg = 0;
405         fi[i].curmsg = 0;
406         fi[i].lowmsg = 0;
407         fi[i].hghmsg = 0;
408         fi[i].others = 0;
409         fi[i].error  = 0;
410
411         if ((ftotal > 0) || !fshort || msg || fpack) {
412                 /*
413                 ** create message structure and get folder info
414                 */
415                 if (!(mp = folder_read(fold))) {
416                         admonish(NULL, "unable to read folder %s", fold);
417                         return 0;
418                 }
419
420                 /* set the current message */
421                 if (msg && !sfold(mp, msg))
422                         retval = 0;
423
424                 if (fpack) {
425                         if (folder_pack(&mp, fverb) == -1)
426                                 done(1);
427                         seq_save(mp);  /* synchronize the sequences */
428                         context_save();  /* save the context file */
429                 }
430
431                 /* record info for this folder */
432                 if ((ftotal > 0) || !fshort) {
433                         fi[i].nummsg = mp->nummsg;
434                         fi[i].curmsg = mp->curmsg;
435                         fi[i].lowmsg = mp->lowmsg;
436                         fi[i].hghmsg = mp->hghmsg;
437                         fi[i].others = other_files(mp);
438                 }
439
440                 folder_free(mp); /* free folder/message structure */
441         }
442
443         *crawl_children = (frecurse && (fshort || fi[i].others)
444                 && (fi[i].error == 0));
445         return retval;
446 }
447
448 static boolean
449 get_folder_info_callback(char *fold, void *baton)
450 {
451         boolean crawl_children;
452         get_folder_info_body(fold, NULL, &crawl_children);
453         fflush(stdout);
454         return crawl_children;
455 }
456
457 static int
458 get_folder_info(char *fold, char *msg)
459 {
460         boolean crawl_children;
461         int retval;
462
463         retval = get_folder_info_body(fold, msg, &crawl_children);
464
465         if (crawl_children) {
466                 crawl_folders(fold, get_folder_info_callback, NULL);
467         }
468 return retval;
469 }
470
471 /*
472 ** Print folder information
473 */
474
475 static void
476 print_folders(void)
477 {
478         int i, len, hasempty = 0, curprinted;
479         int maxlen = 0, maxnummsg = 0, maxlowmsg = 0;
480         int maxhghmsg = 0, maxcurmsg = 0, total_msgs = 0;
481         int nummsgdigits, lowmsgdigits;
482         int hghmsgdigits, curmsgdigits;
483         char tmpname[BUFSIZ];
484
485         /*
486         ** compute a few values needed to for
487         ** printing various fields
488         */
489         for (i = 0; i < total_folders; i++) {
490                 /* length of folder name */
491                 len = strlen(fi[i].name);
492                 if (len > maxlen)
493                         maxlen = len;
494
495                 /* If folder has error, skip the rest */
496                 if (fi[i].error)
497                         continue;
498
499                 /* calculate total number of messages */
500                 total_msgs += fi[i].nummsg;
501
502                 /* maximum number of messages */
503                 if (fi[i].nummsg > maxnummsg)
504                         maxnummsg = fi[i].nummsg;
505
506                 /* maximum low message */
507                 if (fi[i].lowmsg > maxlowmsg)
508                         maxlowmsg = fi[i].lowmsg;
509
510                 /* maximum high message */
511                 if (fi[i].hghmsg > maxhghmsg)
512                         maxhghmsg = fi[i].hghmsg;
513
514                 /* maximum current message */
515                 if (fi[i].curmsg >= fi[i].lowmsg &&
516                         fi[i].curmsg <= fi[i].hghmsg &&
517                         fi[i].curmsg > maxcurmsg)
518                         maxcurmsg = fi[i].curmsg;
519
520                 /* check for empty folders */
521                 if (fi[i].nummsg == 0)
522                         hasempty = 1;
523         }
524         nummsgdigits = num_digits(maxnummsg);
525         lowmsgdigits = num_digits(maxlowmsg);
526         hghmsgdigits = num_digits(maxhghmsg);
527         curmsgdigits = num_digits(maxcurmsg);
528
529         if (hasempty && nummsgdigits < 2)
530                 nummsgdigits = 2;
531
532         /*
533         ** Print folder information
534         */
535         if (all || fshort || ftotal < 1) {
536                 for (i = 0; i < total_folders; i++) {
537                         if (fshort) {
538                                 printf("%s\n", fi[i].name);
539                                 continue;
540                         }
541
542                         /* Add `+' to end of name, if folder is current */
543                         if (strcmp(folder, fi[i].name)!=0)
544                                 snprintf(tmpname, sizeof(tmpname), "%s",
545                                                 fi[i].name);
546                         else
547                                 snprintf(tmpname, sizeof(tmpname), "%s+",
548                                                 fi[i].name);
549
550                         if (fi[i].error) {
551                                 printf("%-*s is unreadable\n", maxlen+1,
552                                                 tmpname);
553                                 continue;
554                         }
555
556                         printf("%-*s ", maxlen+1, tmpname);
557
558                         curprinted = 0; /* remember if we print cur */
559                         if (fi[i].nummsg == 0) {
560                                 printf("has %*s messages%*s", nummsgdigits, "no", fi[i].others ? lowmsgdigits + hghmsgdigits + 5 : 0, "");
561                         } else {
562                                 printf("has %*d message%s  (%*d-%*d)",
563                                                 nummsgdigits, fi[i].nummsg,
564                                                 (fi[i].nummsg == 1) ?
565                                                 " " : "s",
566                                                 lowmsgdigits, fi[i].lowmsg,
567                                                 hghmsgdigits, fi[i].hghmsg);
568                                 if (fi[i].curmsg >= fi[i].lowmsg && fi[i].curmsg <= fi[i].hghmsg) {
569                                         curprinted = 1;
570                                         printf("; cur=%*d", curmsgdigits,
571                                                         fi[i].curmsg);
572                                 }
573                         }
574
575                         if (fi[i].others)
576                                 printf(";%*s (others)", curprinted ?
577                                                 0 : curmsgdigits + 6, "");
578                         printf("\n");
579                 }
580         }
581
582         /*
583         ** Print folder/message totals
584         */
585         if (ftotal > 0 || (all && !fshort && ftotal >= 0)) {
586                 if (all)
587                         printf("\n");
588                 printf("TOTAL = %d message%c in %d folder%s\n",
589                                 total_msgs, total_msgs != 1 ? 's' : ' ',
590                                 total_folders, total_folders != 1 ? "s" : "");
591         }
592
593         fflush(stdout);
594 }
595
596 /*
597 ** Set the current message and sychronize sequences
598 */
599
600 static int
601 sfold(struct msgs *mp, char *msg)
602 {
603         /* parse the message range/sequence/name and set SELECTED */
604         if (!m_convert(mp, msg))
605                 return 0;
606
607         if (mp->numsel > 1) {
608                 admonish(NULL, "only one message at a time!");
609                 return 0;
610         }
611         seq_setprev(mp);  /* set the previous-sequence */
612         seq_setcur(mp, mp->lowsel);  /* set current message */
613         seq_save(mp);  /* synchronize message sequences */
614         context_save();  /* save the context file */
615
616         return 1;
617 }
618
619
620 /*
621 ** Do the read only folders
622 */
623
624 static void
625 readonly_folders(void)
626 {
627         int atrlen;
628         char atrcur[BUFSIZ];
629         register struct node *np;
630
631         snprintf(atrcur, sizeof(atrcur), "atr-%s-", seq_cur);
632         atrlen = strlen(atrcur);
633
634         for (np = m_defs; np; np = np->n_next)
635                 if (strncmp(np->n_name, atrcur, atrlen)==0 &&
636                                 strncmp(np->n_name+atrlen, nmhdir, strlen(nmhdir))!=0)
637                         /* Why do we exclude absolute path names? --meillo */
638                         get_folder_info(np->n_name + atrlen, NULL);
639 }
640
641
642 /*
643 ** pack (renumber) the messages in a folder
644 ** into a contiguous range from 1 to n.
645 ** Return -1 if error, else return 0.
646 */
647 static int
648 folder_pack(struct msgs **mpp, int verbose)
649 {
650         int hole, msgnum, newcurrent = 0;
651         char newmsg[BUFSIZ], oldmsg[BUFSIZ];
652         struct msgs *mp;
653
654         mp = *mpp;
655
656         /*
657         ** Just return if folder is empty.
658         */
659         if (mp->nummsg == 0)
660                 return 0;
661
662         /*
663         ** Make sure we have message status space allocated
664         ** for all numbers from 1 to current high message.
665         */
666         if (mp->lowoff > 1) {
667                 if ((mp = folder_realloc(mp, 1, mp->hghmsg)))
668                         *mpp = mp;
669                 else {
670                         advise(NULL, "unable to allocate folder storage");
671                         return -1;
672                 }
673         }
674
675         for (msgnum = mp->lowmsg, hole = 1; msgnum <= mp->hghmsg; msgnum++) {
676                 if (does_exist(mp, msgnum)) {
677                         if (msgnum != hole) {
678                                 strncpy(newmsg, m_name(hole), sizeof(newmsg));
679                                 strncpy(oldmsg, m_name(msgnum), sizeof(oldmsg));
680                                 if (verbose)
681                                         printf("message %s becomes %s\n", oldmsg, newmsg);
682
683                                 /*
684                                 ** Invoke the external refile hook for each
685                                 ** message being renamed.  This is done
686                                 ** before the file is renamed so that the
687                                 ** old message file is around for the hook.
688                                 */
689
690                                 snprintf(oldmsg, sizeof (oldmsg), "%s/%d",
691                                                 mp->foldpath, msgnum);
692                                 snprintf(newmsg, sizeof (newmsg), "%s/%d",
693                                                 mp->foldpath, hole);
694                                 ext_hook("ref-hook", oldmsg, newmsg);
695
696                                 /* move the message file */
697                                 if (rename(oldmsg, newmsg) == -1) {
698                                         advise(newmsg, "unable to rename %s to", oldmsg);
699                                         return -1;
700                                 }
701
702                                 /* check if this is the current message */
703                                 if (msgnum == mp->curmsg)
704                                         newcurrent = hole;
705
706                                 /* copy the attribute flags for this message */
707                                 copy_msg_flags(mp, hole, msgnum);
708
709                                 if (msgnum == mp->lowsel)
710                                         mp->lowsel = hole;
711                                 if (msgnum == mp->hghsel)
712                                         mp->hghsel = hole;
713
714                                 /*
715                                 ** mark that sequence information has
716                                 ** been modified
717                                 */
718                                 mp->msgflags |= SEQMOD;
719                         }
720                         hole++;
721                 }
722         }
723
724         /* record the new number for the high/low message */
725         mp->lowmsg = 1;
726         mp->hghmsg = hole - 1;
727
728         if (newcurrent)
729                 seq_setcur(mp, newcurrent);
730
731         return 0;
732 }