2 * Incoming Mail Checker
4 * (c) 2005 Martin Mares <mj@ucw.cz>
22 static int check_interval = 30;
23 static int force_refresh;
24 static int lock_mboxes;
25 static time_t last_scan_time;
26 static char *run_cmd = "mutt -f %s";
35 int last_size, last_pos;
39 int last_total, last_new;
43 static clist mboxes, hilites, patterns;
44 static struct mbox **mbox_array;
45 static int num_mboxes;
47 static void redraw_line(int i);
48 static void redraw_all(void);
53 char *c = strrchr(path, '/');
54 return c ? (c+1) : path;
58 add_mbox(clist *l, char *path, char *name)
60 struct mbox *b = xmalloc(sizeof(*b));
62 b->path = xstrdup(path);
63 b->name = xstrdup(name);
67 cnode *prev = l->head.prev;
68 while (prev != &l->head && strcmp(((struct mbox *)prev)->name, name) > 0)
70 clist_insert_after(&b->n, prev);
73 clist_add_tail(l, &b->n);
79 del_mbox(struct mbox *b)
88 find_mbox(clist *l, char *path)
90 CLIST_FOR_EACH(struct mbox *, b, *l)
91 if (!strcmp(b->path, path))
99 struct passwd *p = getpwuid(getuid());
101 die("You don't exist, go away!");
102 char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 1];
103 sprintf(buf, "/var/mail/%s", p->pw_name);
104 add_mbox(l, buf, "INBOX");
107 static int mb_fd, mb_pos;
108 static unsigned char mb_buf[4096], *mb_cc, *mb_end;
113 mb_cc = mb_end = mb_buf;
120 lseek(mb_fd, pos, SEEK_SET);
127 return mb_pos - (mb_end - mb_cc);
133 int len = read(mb_fd, mb_buf, sizeof(mb_buf));
142 mb_end = mb_buf + len;
151 return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get();
155 mb_check(const char *p, int len)
159 if (mb_get() != *p++)
166 scan_mbox(struct mbox *b, struct stat *st)
170 const char from[] = "\nFrom ";
174 b->total = b->new = 0;
179 /* FIXME: Locking! */
181 mb_fd = open(b->path, O_RDONLY);
184 debug("[open failed: %m] ");
185 b->total = b->new = -1;
191 if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh)
193 mb_seek(b->last_pos);
194 if (mb_check(from, 6))
196 debug("[incremental] ");
201 debug("[incremental failed] ");
207 if (!mb_check(from+1, 5))
209 debug("[inconsistent] ");
210 b->total = b->new = -1;
213 b->total = b->new = 0;
214 b->last_total = b->last_new = 0;
218 b->total = b->last_total;
219 b->new = b->last_new;
224 b->last_pos = mb_tell() - 5;
226 b->last_pos--; // last_pos should be the previous \n character
227 b->last_total = b->total;
228 b->last_new = b->new;
229 while ((c = mb_get()) >= 0 && c != '\n')
241 debug("[truncated] ");
248 if (i < sizeof(buf) - 1)
254 if (!strncasecmp(buf, "Status:", 7))
280 debug("Searching for mailboxes...\n");
282 CLIST_FOR_EACH(struct mbox *, p, patterns)
285 int err = glob(p->path, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g);
286 if (err && err != GLOB_NOMATCH)
287 die("Failed to glob %s: %m", p->path);
288 for (uns i=0; i<g.gl_pathc; i++)
290 char *name = g.gl_pathv[i];
291 struct mbox *b = find_mbox(&mboxes, name);
294 b = add_mbox(&mboxes, name, (p->name ? p->name : mbox_name(name)));
295 if (find_mbox(&hilites, b->name))
297 debug("Discovered mailbox %s (%s) hilited=%d\n", b->name, b->path, b->hilited);
308 CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp)
317 debug("Lost mailbox %s\n", b->name);
325 debug("Reallocating mailbox array...\n");
327 mbox_array = xmalloc(sizeof(struct mbox *) * (num_mboxes+1));
329 CLIST_FOR_EACH(struct mbox *, b, mboxes)
338 debug("Scanning mailboxes...\n");
339 CLIST_FOR_EACH(struct mbox *, b, mboxes)
342 debug("%s: ", b->name);
344 b->force_refresh = 1;
345 if (stat(b->path, &st) < 0)
347 b->total = b->new = -1;
350 else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
353 redraw_line(b->index);
357 b->last_time = st.st_mtime;
358 b->last_size = st.st_size;
359 debug("%d %d (stopped at %d of %d)\n", b->total, b->new, b->last_pos, b->last_size);
362 redraw_line(b->index);
366 debug("not changed\n");
367 b->force_refresh = 0;
371 debug("Scan finished\n");
372 last_scan_time = time(NULL);
375 static int cursor_at, cursor_max;
384 static int attrs[2][2][M_MAX]; // active, hilite, status
392 struct mbox *b = mbox_array[i];
393 int cc = (cursor_at == i);
396 attrset(attrs[cc][hi][M_IDLE]);
402 attrset(attrs[cc][hi][M_NEW]);
403 printw("%-20s ", b->name);
406 else if (b->scanning)
408 attrset(attrs[cc][hi][M_SCAN]);
409 printw("[SCANNING]");
411 else if (b->total < 0)
413 attrset(attrs[cc][hi][M_BAD]);
418 attrset(attrs[cc][hi][M_IDLE]);
419 printw("%4d ", b->total);
422 attrset(attrs[cc][hi][M_NEW]);
423 printw("%4d", b->new);
426 attrset(attrs[cc][hi][M_IDLE]);
429 attrset(attrs[0][0][M_IDLE]);
436 cursor_max = num_mboxes;
437 if (cursor_max > LINES-1)
438 cursor_max = LINES-1;
439 if (cursor_at >= cursor_max)
440 cursor_at = cursor_max - 1;
444 for (int i=0; i<cursor_max; i++)
449 printw("(no mailboxes found)");
462 intrflush(stdscr, FALSE);
463 keypad(stdscr, TRUE);
466 static const int attrs_mono[2][M_MAX] = {
467 [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_BAD] = A_DIM },
468 [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_BAD] = A_DIM },
470 for (int i=0; i<2; i++)
471 for (int j=0; j<M_MAX; j++)
473 attrs[0][i][j] = attrs_mono[i][j];
474 attrs[1][i][j] = attrs_mono[i][j] | A_UNDERLINE;
480 if (COLOR_PAIRS >= 5)
482 init_pair(1, COLOR_YELLOW, COLOR_BLACK);
483 init_pair(2, COLOR_RED, COLOR_BLACK);
484 init_pair(3, COLOR_WHITE, COLOR_BLUE);
485 init_pair(4, COLOR_YELLOW, COLOR_BLUE);
486 init_pair(5, COLOR_RED, COLOR_BLUE);
487 static const int attrs_color[2][2][M_MAX] = {
488 [0][0] = { [M_IDLE] = 0, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1), [M_BAD] = COLOR_PAIR(2) },
489 [0][1] = { [M_IDLE] = A_BOLD, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1) | A_BOLD, [M_BAD] = COLOR_PAIR(2) | A_BOLD },
490 [1][0] = { [M_IDLE] = COLOR_PAIR(3), [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4), [M_BAD] = COLOR_PAIR(5) },
491 [1][1] = { [M_IDLE] = COLOR_PAIR(3) | A_BOLD, [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4) | A_BOLD, [M_BAD] = COLOR_PAIR(5) | A_BOLD },
493 memcpy(attrs, attrs_color, sizeof(attrs));
506 scan_and_redraw(void)
519 if (i >= 0 && i < cursor_max && i != cursor_at)
529 next_active(int since)
534 for (int pass=1; pass >= 0; pass--)
538 if (mbox_array[i]->hilited >= pass && mbox_array[i]->new)
543 i = (i+1) % cursor_max;
544 } while (i != since);
551 fprintf(stderr, "Usage: cm [<options>] <mbox-patterns>\n\
554 -c <interval>\t\tScan mailboxes every <interval> seconds (default is 30)\n\
555 -d\t\t\tLog debug messages to stderr\n\
556 -h <mbox>\t\tHighlight and prefer the specified mailbox\n\
557 -i\t\t\tInclude user's INBOX\n\
558 -l\t\t\tLock mailboxes when scanning\n\
559 -m <cmd>\t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\
565 main(int argc, char **argv)
568 clist_init(&hilites);
569 clist_init(&patterns);
572 while ((c = getopt(argc, argv, "c:dh:ilm:")) >= 0)
576 check_interval = atol(optarg);
577 if (check_interval <= 0)
584 add_mbox(&hilites, optarg, NULL);
587 add_inbox(&patterns);
598 while (optind < argc)
599 add_mbox(&patterns, argv[optind++], NULL);
608 time_t now = time(NULL);
609 int remains = last_scan_time + check_interval - now;
610 if (remains <= 0 || force_refresh)
615 halfdelay((remains > 255) ? 255 : remains);
624 move_cursor(cursor_at+1);
628 move_cursor(cursor_at-1);
638 move_cursor(cursor_max-1);
641 next_active(cursor_at+1);
645 if (cursor_at < cursor_max)
647 struct mbox *b = mbox_array[cursor_at];
648 char cmd[strlen(run_cmd) + strlen(b->path) + 16];
649 sprintf(cmd, run_cmd, b->path);
655 b->force_refresh = 1;