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