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