]> diplodocus.org Git - nmh/blob - uip/new.c
Use va_copy() to get a copy of va_list, instead of using original.
[nmh] / uip / new.c
1 /* new.c -- as new, list all folders with unseen messages
2 * -- as fnext, move to next folder with unseen messages
3 * -- as fprev, move to previous folder with unseen messages
4 * -- as unseen, scan all unseen messages
5 * This code is Copyright (c) 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 * Inspired by Luke Mewburn's new: http://www.mewburn.net/luke/src/new
10 */
11
12 #include <sys/types.h>
13
14 #include <h/mh.h>
15 #include <h/crawl_folders.h>
16 #include "h/done.h"
17 #include <h/utils.h>
18 #include "sbr/lock_file.h"
19 #include "sbr/m_maildir.h"
20
21 #define NEW_SWITCHES \
22 X("mode", 1, MODESW) \
23 X("folders", 1, FOLDERSSW) \
24 X("version", 1, VERSIONSW) \
25 X("help", 1, HELPSW) \
26
27 #define X(sw, minchars, id) id,
28 DEFINE_SWITCH_ENUM(NEW);
29 #undef X
30
31 #define X(sw, minchars, id) { sw, minchars, id },
32 DEFINE_SWITCH_ARRAY(NEW, switches);
33 #undef X
34
35 /* What to do, based on argv[0]. */
36 static enum {
37 RM_NEW,
38 RM_FNEXT,
39 RM_FPREV,
40 RM_UNSEEN
41 } run_mode = RM_NEW;
42
43 /* check_folders uses this to maintain state with both .folders list of
44 * folders and with crawl_folders. */
45 struct list_state {
46 struct node **first, **cur_node;
47 size_t *maxlen;
48 char *cur;
49 char **sequences;
50 struct node *node;
51 };
52
53 /* Return the number of messages in a string list of message numbers. */
54 static int
55 count_messages(char *field)
56 {
57 int total = 0;
58 int j, k;
59 char *cp, **ap;
60
61 field = getcpy(field);
62
63 /* copied from seq_read.c:seq_init */
64 for (ap = brkstring (field, " ", "\n"); *ap; ap++) {
65 if ((cp = strchr(*ap, '-')))
66 *cp++ = '\0';
67 if ((j = m_atoi (*ap)) > 0) {
68 k = cp ? m_atoi (cp) : j;
69
70 total += k - j + 1;
71 }
72 }
73
74 free(field);
75
76 return total;
77 }
78
79 /* Return true if the sequence 'name' is in 'sequences'. */
80 static bool
81 seq_in_list(char *name, char *sequences[])
82 {
83 int i;
84
85 for (i = 0; sequences[i] != NULL; i++) {
86 if (strcmp(name, sequences[i]) == 0) {
87 return true;
88 }
89 }
90
91 return false;
92 }
93
94 /* Return the string list of message numbers from the sequences file, or NULL
95 * if none. */
96 static char *
97 get_msgnums(char *folder, char *sequences[])
98 {
99 char *seqfile = NULL;
100 FILE *fp;
101 int state;
102 char name[NAMESZ], field[NMH_BUFSIZ];
103 char *cp;
104 char *msgnums = NULL, *this_msgnums, *old_msgnums;
105 int failed_to_lock = 0;
106 m_getfld_state_t gstate;
107
108 /* copied from seq_read.c:seq_public */
109 /*
110 * If mh_seq == NULL or if *mh_seq == '\0' (the user has defined
111 * the "mh-sequences" profile entry, but left it empty),
112 * then just return, and do not initialize any public sequences.
113 */
114 if (mh_seq == NULL || *mh_seq == '\0')
115 return NULL;
116
117 /* get filename of sequence file */
118 seqfile = concat(m_maildir(folder), "/", mh_seq, NULL);
119
120 if (seqfile == NULL)
121 return NULL;
122
123 if ((fp = lkfopendata (seqfile, "r", & failed_to_lock)) == NULL) {
124 if (failed_to_lock)
125 adios (seqfile, "failed to lock");
126 free(seqfile);
127 return NULL;
128 }
129
130 /* Use m_getfld2 to scan sequence file */
131 gstate = m_getfld_state_init(fp);
132 for (;;) {
133 int fieldsz = sizeof field;
134 switch (state = m_getfld2(&gstate, name, field, &fieldsz)) {
135 case FLD:
136 case FLDPLUS:
137 if (state == FLDPLUS) {
138 cp = getcpy (field);
139 while (state == FLDPLUS) {
140 fieldsz = sizeof field;
141 state = m_getfld2(&gstate, name, field, &fieldsz);
142 cp = add (field, cp);
143 }
144
145 /* Here's where we differ from seq_public: if it's in a
146 * sequence we want, save the list of messages. */
147 if (seq_in_list(name, sequences)) {
148 this_msgnums = trimcpy(cp);
149 if (msgnums == NULL) {
150 msgnums = this_msgnums;
151 } else {
152 old_msgnums = msgnums;
153 msgnums = concat(old_msgnums, " ",
154 this_msgnums, NULL);
155 free(old_msgnums);
156 free(this_msgnums);
157 }
158 }
159 free (cp);
160 } else {
161 /* and here */
162 if (seq_in_list(name, sequences)) {
163 this_msgnums = trimcpy(field);
164 if (msgnums == NULL) {
165 msgnums = this_msgnums;
166 } else {
167 old_msgnums = msgnums;
168 msgnums = concat(old_msgnums, " ",
169 this_msgnums, NULL);
170 free(old_msgnums);
171 free(this_msgnums);
172 }
173 }
174 }
175
176 continue;
177
178 case BODY:
179 die("no blank lines are permitted in %s", seqfile);
180 break;
181
182 case FILEEOF:
183 break;
184
185 default:
186 die("%s is poorly formatted", seqfile);
187 }
188 break; /* break from for loop */
189 }
190 m_getfld_state_destroy (&gstate);
191
192 lkfclosedata (fp, seqfile);
193
194 free(seqfile);
195
196 return msgnums;
197 }
198
199 /* Check `folder' (of length `len') for interesting messages, filling in the
200 * list in `b'. */
201 static void
202 check_folder(char *folder, size_t len, struct list_state *b)
203 {
204 char *msgnums = get_msgnums(folder, b->sequences);
205 int is_cur = strcmp(folder, b->cur) == 0;
206
207 if (is_cur || msgnums != NULL) {
208 if (*b->first == NULL) {
209 NEW(b->node);
210 *b->first = b->node;
211 } else {
212 NEW(b->node->n_next);
213 b->node = b->node->n_next;
214 }
215 b->node->n_name = folder;
216 b->node->n_field = msgnums;
217
218 if (*b->maxlen < len) {
219 *b->maxlen = len;
220 }
221 }
222
223 /* Save the node for the current folder, so we can fall back to it. */
224 if (is_cur) {
225 *b->cur_node = b->node;
226 }
227 }
228
229 static bool
230 crawl_callback(char *folder, void *baton)
231 {
232 check_folder(folder, strlen(folder), baton);
233 return true;
234 }
235
236 /* Scan folders, returning:
237 * first -- list of nodes for all folders which have desired messages;
238 * if the current folder is listed in .folders, it is also in
239 * the list regardless of whether it has any desired messages
240 * last -- last node in list
241 * cur_node -- node of current folder, if listed in .folders
242 * maxlen -- length of longest folder name
243 *
244 * `cur' points to the name of the current folder, `folders' points to the
245 * name of a .folder (if NULL, crawl all folders), and `sequences' points to
246 * the array of sequences for which to look.
247 *
248 * An empty list is returned as first=last=NULL.
249 */
250 static void
251 check_folders(struct node **first, struct node **last,
252 struct node **cur_node, size_t *maxlen,
253 char *cur, char *folders, char *sequences[])
254 {
255 struct list_state b;
256 FILE *fp;
257 char *line;
258 size_t len;
259
260 *first = *last = *cur_node = NULL;
261 *maxlen = 0;
262
263 b.first = first;
264 b.cur_node = cur_node;
265 b.maxlen = maxlen;
266 b.cur = cur;
267 b.sequences = sequences;
268
269 if (folders == NULL) {
270 if (chdir(m_maildir("")) < 0) {
271 advise (m_maildir(""), "chdir");
272 }
273 crawl_folders(".", crawl_callback, &b);
274 } else {
275 fp = fopen(folders, "r");
276 if (fp == NULL) {
277 die("failed to read %s", folders);
278 }
279 while (vfgets(fp, &line) == OK) {
280 len = strlen(line) - 1;
281 line[len] = '\0';
282 check_folder(mh_xstrdup(line), len, &b);
283 }
284 fclose(fp);
285 }
286
287 if (*first != NULL) {
288 b.node->n_next = NULL;
289 *last = b.node;
290 }
291 }
292
293 /* Return a single string of the `sequences' joined by a space (' '). */
294 static char *
295 join_sequences(char *sequences[])
296 {
297 int i;
298 size_t len = 0;
299 char *result, *cp;
300
301 for (i = 0; sequences[i] != NULL; i++) {
302 len += strlen(sequences[i]) + 1;
303 }
304 result = mh_xmalloc(len + 1);
305
306 for (i = 0, cp = result; sequences[i] != NULL; i++, cp += len + 1) {
307 len = strlen(sequences[i]);
308 memcpy(cp, sequences[i], len);
309 cp[len] = ' ';
310 }
311 /* -1 to overwrite the last delimiter */
312 *--cp = '\0';
313
314 return result;
315 }
316
317 /* Return a struct node for the folder to change to. This is the next
318 * (previous, if RM_FPREV mode) folder with desired messages, or the current
319 * folder if no folders have desired. If RM_NEW or RM_UNSEEN mode, print the
320 * output but don't change folders.
321 *
322 * n_name is the folder to change to, and n_field is the string list of
323 * desired message numbers.
324 */
325 static struct node *
326 doit(char *cur, char *folders, char *sequences[])
327 {
328 struct node *first, *cur_node, *node, *last, *prev;
329 size_t folder_len;
330 int count, total = 0;
331 char *command = NULL, *sequences_s = NULL;
332
333 if (cur == NULL || cur[0] == '\0') {
334 cur = "inbox";
335 }
336
337 check_folders(&first, &last, &cur_node, &folder_len, cur,
338 folders, sequences);
339
340 if (run_mode == RM_FNEXT || run_mode == RM_FPREV) {
341 if (first == NULL) {
342 /* No folders at all... */
343 return NULL;
344 }
345 if (first->n_next == NULL) {
346 /* We have only one node; any desired messages in it? */
347 if (first->n_field == NULL) {
348 return NULL;
349 }
350 return first;
351 }
352 if (cur_node == NULL) {
353 /* Current folder is not listed in .folders, return first. */
354 return first;
355 }
356 } else if (run_mode == RM_UNSEEN) {
357 sequences_s = join_sequences(sequences);
358 }
359
360 for (node = first, prev = NULL;
361 node != NULL;
362 prev = node, node = node->n_next) {
363 if (run_mode == RM_FNEXT) {
364 /* If we have a previous node and it is the current
365 * folder, return this node. */
366 if (prev != NULL && strcmp(prev->n_name, cur) == 0) {
367 return node;
368 }
369 } else if (run_mode == RM_FPREV) {
370 if (strcmp(node->n_name, cur) == 0) {
371 /* Found current folder in fprev mode; if we have a
372 * previous node in the list, return it; else return
373 * the last node. */
374 if (prev)
375 return prev;
376 return last;
377 }
378 } else if (run_mode == RM_UNSEEN) {
379 int status;
380
381 if (node->n_field == NULL) {
382 continue;
383 }
384
385 printf("\n%d %s messages in %s",
386 count_messages(node->n_field),
387 sequences_s,
388 node->n_name);
389 if (strcmp(node->n_name, cur) == 0) {
390 puts(" (*: current folder)");
391 } else {
392 putchar('\n');
393 }
394 fflush(stdout);
395
396 /* TODO: Split enough of scan.c out so that we can call it here. */
397 command = concat("scan +", node->n_name, " ", sequences_s,
398 NULL);
399 status = system(command);
400 if (! WIFEXITED (status)) {
401 adios (command, "system");
402 }
403 free(command);
404 } else {
405 if (node->n_field == NULL) {
406 continue;
407 }
408
409 count = count_messages(node->n_field);
410 total += count;
411
412 printf("%-*s %6d.%c %s\n",
413 (int) folder_len, node->n_name,
414 count,
415 (strcmp(node->n_name, cur) == 0 ? '*' : ' '),
416 node->n_field);
417 }
418 }
419
420 /* If we're fnext, we haven't checked the last node yet. If it's the
421 * current folder, return the first node. */
422 if (run_mode == RM_FNEXT) {
423 assert(last != NULL);
424 if (strcmp(last->n_name, cur) == 0) {
425 return first;
426 }
427 }
428
429 if (run_mode == RM_NEW) {
430 printf("%-*s %6d.\n", (int) folder_len, " total", total);
431 }
432
433 return cur_node;
434 }
435
436 int
437 main(int argc, char **argv)
438 {
439 char **ap, *cp, **argp, **arguments;
440 char help[BUFSIZ];
441 char *folders = NULL;
442 svector_t sequences = svector_create (0);
443 int i = 0;
444 char *unseen;
445 struct node *folder;
446
447 if (nmh_init(argv[0], true, true)) { return 1; }
448
449 arguments = getarguments (invo_name, argc, argv, 1);
450 argp = arguments;
451
452 /*
453 * Parse arguments
454 */
455 while ((cp = *argp++)) {
456 if (*cp == '-') {
457 switch (smatch (++cp, switches)) {
458 case AMBIGSW:
459 ambigsw (cp, switches);
460 done (1);
461 case UNKWNSW:
462 die("-%s unknown", cp);
463
464 case HELPSW:
465 snprintf (help, sizeof(help), "%s [switches] [sequences]",
466 invo_name);
467 print_help (help, switches, 1);
468 done (0);
469 case VERSIONSW:
470 print_version(invo_name);
471 done (0);
472
473 case FOLDERSSW:
474 if (!(folders = *argp++) || *folders == '-')
475 die("missing argument to %s", argp[-2]);
476 continue;
477 case MODESW:
478 if (!(invo_name = *argp++) || *invo_name == '-')
479 die("missing argument to %s", argp[-2]);
480 invo_name = r1bindex(invo_name, '/');
481 continue;
482 }
483 }
484 /* have a sequence argument */
485 if (!seq_in_list(cp, svector_strs (sequences))) {
486 svector_push_back (sequences, cp);
487 ++i;
488 }
489 }
490
491 if (strcmp(invo_name, "fnext") == 0) {
492 run_mode = RM_FNEXT;
493 } else if (strcmp(invo_name, "fprev") == 0) {
494 run_mode = RM_FPREV;
495 } else if (strcmp(invo_name, "unseen") == 0) {
496 run_mode = RM_UNSEEN;
497 }
498
499 if (folders == NULL) {
500 /* will flists */
501 } else {
502 if (folders[0] != '/') {
503 folders = m_maildir(folders);
504 }
505 }
506
507 if (i == 0) {
508 /* no sequence arguments; use unseen */
509 unseen = context_find(usequence);
510 if (unseen == NULL || unseen[0] == '\0') {
511 die("must specify sequences or set %s", usequence);
512 }
513 for (ap = brkstring(unseen, " ", "\n"); *ap; ap++) {
514 svector_push_back (sequences, *ap);
515 ++i;
516 }
517 }
518
519 folder = doit(context_find(pfolder), folders, svector_strs (sequences));
520 if (folder == NULL) {
521 done(0);
522 return 1;
523 }
524
525 if (run_mode == RM_UNSEEN) {
526 /* All the scan(1)s it runs change the current folder, so we
527 * need to put it back. Unfortunately, context_replace lamely
528 * ignores the new value you give it if it is the same one it
529 * has in memory. So, we'll be lame, too. I'm not sure if i
530 * should just change context_replace... */
531 context_replace(pfolder, "defeat_context_replace_optimization");
532 }
533
534 /* update current folder */
535 context_replace(pfolder, folder->n_name);
536
537 if (run_mode == RM_FNEXT || run_mode == RM_FPREV) {
538 printf("%s %s\n", folder->n_name, folder->n_field);
539 }
540
541 context_save();
542
543 svector_free (sequences);
544 done (0);
545 return 1;
546 }