X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=cm.c;h=a3bba1853305c46e21b595475157a6ec37ff5b0a;hb=dbae77bf7318b71cfafdbc8b957ffabcbf437367;hp=09dce992e942ea22dbc6bf48e1ef663ac5b833dd;hpb=e4d98a565f8d3d47bac039a8f6b43bdb222b3215;p=checkmail.git diff --git a/cm.c b/cm.c index 09dce99..a3bba18 100644 --- a/cm.c +++ b/cm.c @@ -1,15 +1,17 @@ /* * Incoming Mail Checker * - * (c) 2005 Martin Mares + * (c) 2005--2007 Martin Mares */ #include #include #include +#include #include #include #include +#include #include #include #include @@ -18,34 +20,129 @@ #include "util.h" #include "clists.h" +#include "charset.h" static int check_interval = 30; static int force_refresh; -static int lock_mboxes; +static int allow_bells = 1; +static int minimum_priority; static time_t last_scan_time; static char *run_cmd = "mutt -f %s"; +struct options { + int priority; + int hide; + int hide_if_empty; + int highlight; + int beep; + int snippets; + int show_flagged; + int sender_personal; + int sender_mbox; +}; + +struct option_node { + cnode n; + struct options o; + char pattern[1]; +}; + +struct pattern_node { + cnode n; + char *name; + char pattern[1]; +}; + +static clist options, patterns; +static struct options global_options = { + .sender_personal = 1 +}; + struct mbox { cnode n; + struct options o; + char *name; + char *path; int index; - int hilited; int scanning; int seen; time_t last_time; int last_size, last_pos; - char *name; - char *path; - int total, new; - int last_total, last_new; + int total, new, flagged; + int last_total, last_new, last_flagged; + int last_beep_new; int force_refresh; + int snippet_is_new; + char snippet[256]; }; -static clist mboxes, hilites, patterns; +static clist mboxes; static struct mbox **mbox_array; -static int num_mboxes; +static int num_mboxes, mbox_array_size; static void redraw_line(int i); -static void redraw_all(void); +static void rethink_display(void); + +static void +add_pattern(char *patt) +{ + struct pattern_node *n = xmalloc(sizeof(*n) + strlen(patt)); + strcpy(n->pattern, patt); + if (patt = strchr(n->pattern, '=')) + { + *patt++ = 0; + n->name = patt; + } + else + n->name = NULL; + clist_add_tail(&patterns, &n->n); +} + +static void +add_inbox(void) +{ + struct passwd *p = getpwuid(getuid()); + if (!p) + die("You don't exist, go away!"); + char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 7]; + sprintf(buf, "/var/mail/%s=INBOX", p->pw_name); + add_pattern(buf); +} + +static void +init_options(struct options *o) +{ + o->priority = -1; + o->hide = -1; + o->hide_if_empty = -1; + o->beep = -1; + o->highlight = -1; + o->snippets = -1; + o->show_flagged = -1; + o->sender_personal = -1; + o->sender_mbox = -1; +} + +static void +setup_options(struct mbox *b) +{ + b->o = global_options; + CLIST_FOR_EACH(struct option_node *, n, options) + if (!fnmatch(n->pattern, b->name, 0)) + { + debug("\tApplied options %s\n", n->pattern); +#define MERGE(f) if (n->o.f >= 0) b->o.f = n->o.f + MERGE(priority); + MERGE(hide); + MERGE(hide_if_empty); + MERGE(highlight); + MERGE(beep); + MERGE(snippets); + MERGE(show_flagged); + MERGE(sender_personal); + MERGE(sender_mbox); + } +} static char * mbox_name(char *path) @@ -93,15 +190,47 @@ find_mbox(clist *l, char *path) return NULL; } +static inline int +mbox_active_p(struct mbox *b) +{ + if (b->o.priority < minimum_priority) + return 0; + if (b->o.hide) + return 0; + return 1; +} + +static inline int +mbox_visible_p(struct mbox *b) +{ + if (!mbox_active_p(b)) + return 0; + if (b->scanning < 0) + return 1; + if (b->o.hide_if_empty && !b->total) + return 0; + return 1; +} + static void -add_inbox(clist *l) +prepare_snippet(struct mbox *b, char *sender, char *subject) { - struct passwd *p = getpwuid(getuid()); - if (!p) - die("You don't exist, go away!"); - char buf[sizeof("/var/mail/") + strlen(p->pw_name) + 1]; - sprintf(buf, "/var/mail/%s", p->pw_name); - add_mbox(l, buf, "INBOX"); + while (*sender == ' ' || *sender == '\t') + sender++; + while (*subject == ' ' || *subject == '\t') + subject++; + + char *pos = b->snippet; + char *term = b->snippet + sizeof(b->snippet) - 1; + if (sender[0] && (b->o.sender_mbox || b->o.sender_personal)) + { + add_addr_snippet(&pos, term, sender, b->o.sender_mbox, b->o.sender_personal); + add_snippet(&pos, term, ": "); + } + if (subject[0]) + add_subject_snippet(&pos, term, subject); + else + add_snippet(&pos, term, "No subject"); } static int mb_fd, mb_pos; @@ -114,7 +243,7 @@ mb_reset(int pos) mb_pos = pos; } -static int +static void mb_seek(uns pos) { lseek(mb_fd, pos, SEEK_SET); @@ -151,6 +280,13 @@ mb_get(void) return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get(); } +static void +mb_unget(int c) +{ + if (c >= 0) + mb_cc--; +} + static int mb_check(const char *p, int len) { @@ -165,13 +301,13 @@ mb_check(const char *p, int len) static void scan_mbox(struct mbox *b, struct stat *st) { - char buf[1024]; + char buf[1024], sender[1024], subject[1024]; int c; const char from[] = "\nFrom "; if (!st->st_size) { - b->total = b->new = 0; + b->total = b->new = b->flagged = 0; b->last_pos = 0; return; } @@ -182,7 +318,7 @@ scan_mbox(struct mbox *b, struct stat *st) if (mb_fd < 0) { debug("[open failed: %m] "); - b->total = b->new = -1; + b->total = b->new = b->flagged = -1; return; } mb_reset(0); @@ -207,16 +343,18 @@ scan_mbox(struct mbox *b, struct stat *st) if (!mb_check(from+1, 5)) { debug("[inconsistent] "); - b->total = b->new = -1; + b->total = b->new = b->flagged = -1; goto done; } - b->total = b->new = 0; - b->last_total = b->last_new = 0; + b->total = b->new = b->flagged = 0; + b->last_total = b->last_new = b->last_flagged = 0; + b->snippet_is_new = 0; } else { b->total = b->last_total; b->new = b->last_new; + b->flagged = b->last_flagged; } for(;;) @@ -226,10 +364,14 @@ scan_mbox(struct mbox *b, struct stat *st) b->last_pos--; // last_pos should be the previous \n character b->last_total = b->total; b->last_new = b->new; + b->last_flagged = b->flagged; while ((c = mb_get()) >= 0 && c != '\n') ; int new = 1; + int flagged = 0; + sender[0] = 0; + subject[0] = 0; for (;;) { uns i = 0; @@ -242,7 +384,19 @@ scan_mbox(struct mbox *b, struct stat *st) goto done; } if (c == '\n') - break; + { + int fold = -1; + do + { + fold++; + c = mb_get(); + } + while (c == ' ' || c == '\t'); + mb_unget(c); + if (!fold) + break; + c = ' '; + } if (c == '\r') continue; if (i < sizeof(buf) - 1) @@ -253,11 +407,24 @@ scan_mbox(struct mbox *b, struct stat *st) break; if (!strncasecmp(buf, "Status:", 7)) new = 0; + else if (!strncasecmp(buf, "X-Status:", 9) && strchr(buf+9, 'F')) + flagged = 1; + else if (!strncasecmp(buf, "From:", 5)) + strcpy(sender, buf+5); + else if (!strncasecmp(buf, "Subject:", 8)) + strcpy(subject, buf+8); } b->total++; if (new) b->new++; + if (flagged) + b->flagged++; + if (new || (flagged && !b->snippet_is_new)) + { + b->snippet_is_new = new; + prepare_snippet(b, sender, subject); + } int ct = 1; while (from[ct]) @@ -279,13 +446,13 @@ scan(void) { debug("Searching for mailboxes...\n"); last_scan_time = time(NULL); - int changed = 0; - CLIST_FOR_EACH(struct mbox *, p, patterns) + CLIST_FOR_EACH(struct pattern_node *, p, patterns) { + debug("Trying pattern %s (name %s)\n", p->pattern, p->name); glob_t g; - int err = glob(p->path, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g); + int err = glob(p->pattern, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g); if (err && err != GLOB_NOMATCH) - die("Failed to glob %s: %m", p->path); + die("Failed to glob %s: %m", p->pattern); for (uns i=0; iname ? p->name : mbox_name(name))); - if (find_mbox(&hilites, b->name)) - b->hilited = 1; - debug("Discovered mailbox %s (%s) hilited=%d\n", b->name, b->path, b->hilited); + debug("Discovered mailbox %s (%s)\n", b->name, b->path); + setup_options(b); b->scanning = -1; - changed = 1; } b->seen = 1; } globfree(&g); } - num_mboxes = 0; struct mbox *tmp; CLIST_FOR_EACH_DELSAFE(struct mbox *, b, mboxes, tmp) { if (b->seen) - { - num_mboxes++; - b->seen = 0; - } + b->seen = 0; else { debug("Lost mailbox %s\n", b->name); - changed = 1; del_mbox(b); } } - if (changed) - { - debug("Reallocating mailbox array...\n"); - free(mbox_array); - mbox_array = xmalloc(sizeof(struct mbox *) * (num_mboxes+1)); - int i = 0; - CLIST_FOR_EACH(struct mbox *, b, mboxes) - { - b->index = i; - mbox_array[i++] = b; - } - redraw_all(); - refresh(); - } + rethink_display(); debug("Scanning mailboxes...\n"); CLIST_FOR_EACH(struct mbox *, b, mboxes) { struct stat st; debug("%s: ", b->name); + if (!mbox_active_p(b)) + { + debug("inactive\n"); + continue; + } if (force_refresh) b->force_refresh = 1; if (stat(b->path, &st) < 0) { - b->total = b->new = -1; + b->total = b->new = b->flagged = -1; debug("%m\n"); } else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh) @@ -357,7 +509,7 @@ scan(void) scan_mbox(b, &st); b->last_time = st.st_mtime; b->last_size = st.st_size; - debug("%d %d (stopped at %d of %d)\n", b->total, b->new, b->last_pos, b->last_size); + debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size); b->scanning = 0; redraw_line(b->index); @@ -371,6 +523,7 @@ scan(void) debug("Scan finished\n"); last_scan_time = time(NULL); + rethink_display(); } static int cursor_at, cursor_max; @@ -379,6 +532,7 @@ enum { M_IDLE, M_SCAN, M_NEW, + M_FLAG, M_BAD, M_MAX }; @@ -392,7 +546,7 @@ redraw_line(int i) { struct mbox *b = mbox_array[i]; int cc = (cursor_at == i); - int hi = b->hilited; + int hi = b->o.highlight; attrset(attrs[cc][hi][M_IDLE]); if (cc) @@ -401,6 +555,8 @@ redraw_line(int i) printw(" "); if (b->new) attrset(attrs[cc][hi][M_NEW]); + else if (b->flagged) + attrset(attrs[cc][hi][M_FLAG]); printw("%-20s ", b->name); if (b->scanning < 0) ; @@ -418,6 +574,7 @@ redraw_line(int i) { attrset(attrs[cc][hi][M_IDLE]); printw("%6d ", b->total); + int snip = 0; if (b->new) { attrset(attrs[cc][hi][M_NEW]); @@ -427,15 +584,33 @@ redraw_line(int i) if (age < 0) age = 0; if (age < 3600) - printw("%2d min", age/60); + printw("%2d min ", age/60); else if (age < 86400) - printw("%2d hrs", age/3600); + printw("%2d hrs ", age/3600); + else + printw(" "); + snip = 1; + } + else if (b->flagged) + { + attrset(attrs[cc][hi][M_FLAG]); + printw("%6d ", b->flagged); + attrset(attrs[cc][hi][M_IDLE]); + printw(" "); + attrset(attrs[cc][0][M_FLAG]); /* We avoid the highlight intentionally */ + snip = b->o.show_flagged; + } + if (snip && b->o.snippets && b->snippet[0]) + { + int xx, yy; + getyx(stdscr, yy, xx); + int remains = COLS-1-xx; + if (remains > 2) + printw("%-.*s", remains, b->snippet); } } - attrset(attrs[cc][hi][M_IDLE]); } - else - attrset(attrs[0][0][M_IDLE]); + attrset(attrs[0][0][M_IDLE]); clrtoeol(); } @@ -456,11 +631,50 @@ redraw_all(void) if (!cursor_max) { printw("(no mailboxes found)"); + clrtoeol(); move(1, 0); } clrtobot(); } +static void +rethink_display(void) +{ + int i = 0; + int changed = 0; + int beeeep = 0; + CLIST_FOR_EACH(struct mbox *, b, mboxes) + if (mbox_visible_p(b)) + { + b->index = i; + if (i >= num_mboxes || mbox_array[i] != b) + { + changed = 1; + if (i >= mbox_array_size) + { + mbox_array_size = (mbox_array_size ? 2*mbox_array_size : 16); + mbox_array = xrealloc(mbox_array, sizeof(struct mbox *) * mbox_array_size); + } + mbox_array[i] = b; + } + if (b->o.beep && b->new > b->last_beep_new) + beeeep = 1; + b->last_beep_new = b->new; + i++; + } + if (i != num_mboxes) + changed = 1; + num_mboxes = i; + + if (changed) + { + redraw_all(); + refresh(); + } + if (beeeep && allow_bells) + beep(); +} + static void term_init(void) { @@ -473,8 +687,8 @@ term_init(void) curs_set(0); static const int attrs_mono[2][M_MAX] = { - [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_BAD] = A_DIM }, - [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_BAD] = A_DIM }, + [0] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_BOLD, [M_FLAG] = 0, [M_BAD] = A_DIM }, + [1] = { [M_IDLE] = 0, [M_SCAN] = A_BOLD, [M_NEW] = A_REVERSE | A_BOLD, [M_FLAG] = A_REVERSE, [M_BAD] = A_DIM }, }; for (int i=0; i<2; i++) for (int j=0; j= 0; pass--) + since = (since+cursor_max) % cursor_max; + step = (step+cursor_max) % cursor_max; + int besti = -1; + int bestp = -1; + int i = since; + do { - int i = since; - do { - if (mbox_array[i]->hilited >= pass && mbox_array[i]->new) - { - move_cursor(i); - return; - } - i = (i+1) % cursor_max; - } while (i != since); + struct mbox *b = mbox_array[i]; + if (b->new && b->o.priority > bestp) + { + besti = i; + bestp = b->o.priority; + } + i = (i+step) % cursor_max; } + while (i != since); + if (besti >= 0) + move_cursor(besti); } +#define STR2(c) #c +#define STR(c) STR2(c) + static void NONRET usage(void) { - fprintf(stderr, "Usage: cm [] \n\ + fprintf(stderr, "Usage: cm [] [ | [=]] ...\n\ \n\ Options:\n\ -c \t\tScan mailboxes every seconds (default is 30)\n\ -d\t\t\tLog debug messages to stderr\n\ --h \t\tHighlight and prefer the specified mailbox\n\ -i\t\t\tInclude user's INBOX\n\ --l\t\t\tLock mailboxes when scanning\n\ -m \t\tCommand to run on the selected mailbox, %%s gets replaced by mailbox path\n\ +-o =\tSet mailbox options\n\ +-o \t\tSet default options for all mailboxes\n\ +-p \t\tSet minimum priority to show\n\ +\n\ +Mailbox options (set with `-o', use upper case to negate):\n\ +0-9\t\t\tSet mailbox priority (0=default)\n\ +b\t\t\tBeep when a message arrives\n\ +e\t\t\tHide from display if empty\n\ +f\t\t\tShow flagged messages if there are no new ones\n\ +h\t\t\tHide from display\n\ +m\t\t\tShow mailbox name of the sender\n\ +p\t\t\tShow personal info (full name) of the sender\n\ +s\t\t\tShow message snippets\n\ +t\t\t\tHighlight the entry\n\ \n\ -CheckMail 0.1, (c) 2005 Martin Mares \n\ +CheckMail " STR(VERSION) ", (c) " STR(YEAR) " Martin Mares \n\ It can be freely distributed and used according to the GNU GPL v2.\n\ "); exit(1); } +static void +parse_options(char *c) +{ + struct options *o; + char *sep; + if (sep = strchr(c, '=')) + { + struct option_node *n = xmalloc(sizeof(*n) + sep-c); + memcpy(n->pattern, c, sep-c); + n->pattern[sep-c] = 0; + clist_add_tail(&options, &n->n); + o = &n->o; + init_options(o); + c = sep+1; + } + else + o = &global_options; + + int x; + while (x = *c++) + if (x >= '0' && x <= '9') + o->priority = x - '0'; + else + { + int value = !!islower(x); + switch (tolower(x)) + { + case 'b': + o->beep = value; + break; + case 'e': + o->hide_if_empty = value; + break; + case 'f': + o->show_flagged = value; + break; + case 'h': + o->hide = value; + break; + case 'm': + o->sender_mbox = value; + break; + case 'p': + o->sender_personal = value; + break; + case 's': + o->snippets = value; + break; + case 't': + o->highlight = value; + break; + default: + fprintf(stderr, "Invalid mailbox option `%c'\n", x); + usage(); + } + } +} + int main(int argc, char **argv) { clist_init(&mboxes); - clist_init(&hilites); + clist_init(&options); clist_init(&patterns); int c; - while ((c = getopt(argc, argv, "c:dh:ilm:")) >= 0) + while ((c = getopt(argc, argv, "c:dim:o:p:")) >= 0) switch (c) { case 'c': @@ -592,27 +892,28 @@ main(int argc, char **argv) case 'd': debug_mode++; break; - case 'h': - add_mbox(&hilites, optarg, NULL); - break; case 'i': - add_inbox(&patterns); - break; - case 'l': - lock_mboxes = 1; + add_inbox(); break; case 'm': run_cmd = optarg; break; + case 'o': + parse_options(optarg); + break; + case 'p': + minimum_priority = atol(optarg); + break; default: usage(); } while (optind < argc) - add_mbox(&patterns, argv[optind++], NULL); + add_pattern(argv[optind++]); + charset_init(); term_init(); scan_and_redraw(); - next_active(0); + next_active(0, 1); int should_exit = 0; while (!should_exit) @@ -639,7 +940,7 @@ main(int argc, char **argv) case KEY_UP: move_cursor(cursor_at-1); break; - case '0': + case '^': case KEY_HOME: case KEY_PPAGE: move_cursor(0); @@ -650,7 +951,10 @@ main(int argc, char **argv) move_cursor(cursor_max-1); break; case '\t': - next_active(cursor_at+1); + next_active(cursor_at+1, 1); + break; + case '`': + next_active(cursor_at-1, -1); break; case '\r': case '\n': @@ -668,9 +972,30 @@ main(int argc, char **argv) scan_and_redraw(); } break; + case 'l' & 0x1f: + clearok(stdscr, TRUE); + redraw_all(); + refresh(); + break; case 'r' & 0x1f: force_refresh = 1; break; + case 'b': + allow_bells = 1; + print_status("Bells and whistles are now enabled. Toot!"); + break; + case 'B': + allow_bells = 0; + print_status("Bells and whistles are now disabled. Pssst!"); + break; + default: + if (ch >= '0' && ch <= '9') + { + minimum_priority = ch - '0'; + scan_and_redraw(); + } + else + debug("Pressed unknown key %d\n", ch); } refresh(); }