X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=cm.c;h=9bc036672c4139ee175a48b34239bf0b0698bc52;hb=8d4bc94b9732a8a8fb966fc61a5d6a97568c353d;hp=8b2b03fa64c62f0ef55d364cb7604af2f439b288;hpb=7f9f1699540d9726c9131ca65277f04d7ab223f5;p=checkmail.git diff --git a/cm.c b/cm.c index 8b2b03f..9bc0366 100644 --- a/cm.c +++ b/cm.c @@ -1,25 +1,26 @@ /* * Incoming Mail Checker * - * (c) 2005--2010 Martin Mares + * (c) 2005--2015 Martin Mares */ #define _GNU_SOURCE -#include -#include -#include #include -#include +#include #include -#include #include -#include -#include +#include +#include #include -#include +#include +#include +#include +#include #include #include +#include +#include #ifdef CONFIG_WIDE_CURSES #include @@ -38,6 +39,7 @@ static int allow_osd = 1; static int minimum_priority; static time_t last_scan_time; static char *run_cmd = "mutt -f %s"; +static int simple_tab; struct options { int priority; @@ -52,6 +54,7 @@ struct options { int hotkey; int led; int osd; + int unread_is_new; }; struct option_node { @@ -71,6 +74,8 @@ static struct options global_options = { .sender_personal = 1 }; +#define MDIR_MAX_NAME_LEN 128 + struct mbox { cnode n; struct options o; @@ -80,14 +85,17 @@ struct mbox { int scanning; int seen; time_t last_time; + time_t best_time; + time_t display_valid_until; int last_size, last_pos; int total, new, flagged; int last_total, last_new, last_flagged; int last_beep_new; int force_refresh; - int snippet_is_new; + int best_is_new; char sender_snippet[128]; char subject_snippet[128]; + char mdir_best[MDIR_MAX_NAME_LEN]; }; static clist mboxes; @@ -146,6 +154,7 @@ init_options(struct options *o) o->hotkey = -1; o->led = -1; o->osd = -1; + o->unread_is_new = -1; } static void @@ -169,6 +178,7 @@ setup_options(struct mbox *b) MERGE(hotkey); MERGE(led); MERGE(osd); + MERGE(unread_is_new); } } @@ -288,7 +298,7 @@ build_snippet(char *buf, char *term, struct mbox *b) add_snippet(&buf, term, b->subject_snippet); } -static int mb_fd, mb_pos; +static int mb_fd, mb_pos, mb_seekable; static unsigned char mb_buf[4096], *mb_cc, *mb_end; static void @@ -353,14 +363,84 @@ mb_check(const char *p, int len) return 1; } +static void +mb_skip(int len) +{ + while (len) + { + int avail = mb_end - mb_cc; + if (!avail) + { + if (mb_seekable && len >= (int) sizeof(mb_buf)) + { + int next = len - len % sizeof(mb_buf); + mb_seek(mb_tell() + next); + len -= next; + continue; + } + if (mb_ll_get() < 0) + return; + len--; + } + else + { + int next = (avail < len) ? avail : len; + len -= next; + mb_cc += next; + } + } +} + +#define HDR_BUF_SIZE 1024 + +static int +read_hdr_field(char *buf) +{ + uns i = 0; + for (;;) + { + int c = mb_get(); + if (c < 0) + { + debug("[truncated] "); + return -1; + } + if (c == '\n') + { + if (!i) + 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 < HDR_BUF_SIZE - 1) + buf[i++] = c; + } + buf[i] = 0; + if (debug_mode > 2) + debug("Header: <%s>\n", buf); + return buf[0] ? 1 : 0; +} + static void scan_mbox(struct mbox *b, struct stat *st) { - char buf[1024], sender[1024], subject[1024]; + char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE]; int c; int compressed = 0; const char from[] = "\nFrom "; + b->best_time = st->st_mtime; if (!st->st_size) { b->total = b->new = b->flagged = 0; @@ -368,8 +448,6 @@ scan_mbox(struct mbox *b, struct stat *st) return; } - /* FIXME: Should we do some locking? */ - mb_fd = open(b->path, O_RDONLY); if (mb_fd < 0) { @@ -377,6 +455,7 @@ scan_mbox(struct mbox *b, struct stat *st) b->total = b->new = b->flagged = -1; return; } + mb_seekable = 1; char signature[2]; c = read(mb_fd, signature, 2); @@ -405,11 +484,12 @@ scan_mbox(struct mbox *b, struct stat *st) close(mb_fd); mb_fd = fds[0]; compressed = 1; + mb_seekable = 0; } mb_reset(0); int incremental = 0; - if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh && !compressed) + if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh && mb_seekable) { mb_seek(b->last_pos); if (mb_check(from, 6)) @@ -433,7 +513,7 @@ scan_mbox(struct mbox *b, struct stat *st) } b->total = b->new = b->flagged = 0; b->last_total = b->last_new = b->last_flagged = 0; - b->snippet_is_new = 0; + b->best_is_new = 0; } else { @@ -453,51 +533,31 @@ scan_mbox(struct mbox *b, struct stat *st) while ((c = mb_get()) >= 0 && c != '\n') ; + int content_length = -1; int new = 1; int flagged = 0; sender[0] = 0; subject[0] = 0; for (;;) { - uns i = 0; - for (;;) - { - c = mb_get(); - if (c < 0) - { - debug("[truncated] "); - goto done; - } - if (c == '\n') - { - 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) - buf[i++] = c; - } - buf[i] = 0; - if (!buf[0]) + int c = read_hdr_field(buf); + if (c < 0) + goto done; + if (!c) break; if (!strncasecmp(buf, "Status:", 7)) - new = 0; + { + if (!b->o.unread_is_new || strchr(buf + 7, 'R')) + 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); + else if (!strncasecmp(buf, "Content-Length:", 15)) + content_length = atoi(buf + 15); } b->total++; @@ -506,13 +566,16 @@ scan_mbox(struct mbox *b, struct stat *st) if (flagged) b->flagged++; if (debug_mode > 1) - debug("new=%d flagged=%d sender=<%s> subject=<%s>\n", new, flagged, sender, subject); - if (new || (flagged && !b->snippet_is_new)) + debug("new=%d flagged=%d len=%d sender=<%s> subject=<%s>\n", new, flagged, content_length, sender, subject); + if (new || (flagged && !b->best_is_new)) { - b->snippet_is_new = new; + b->best_is_new = new; prepare_snippets(b, sender, subject); } + if (content_length >= 0) + mb_skip(content_length); + int ct = 1; while (from[ct]) { @@ -534,6 +597,138 @@ scan_mbox(struct mbox *b, struct stat *st) } } +static time_t +mdir_mtime(struct mbox *b) +{ + time_t mtime = 0; + char path[strlen(b->path) + 5]; + + for (int new=0; new<2; new++) + { + sprintf(path, "%s/%s", b->path, (new ? "new" : "cur")); + struct stat st; + if (stat(path, &st) < 0) + debug("[cannot stat %s] ", path); + else if (st.st_mtime > mtime) + mtime = st.st_mtime; + } + return mtime; +} + +static void +mdir_get_snippet(struct mbox *b) +{ + char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE]; + sender[0] = 0; + subject[0] = 0; + + char path[strlen(b->path) + MDIR_MAX_NAME_LEN]; + sprintf(path, "%s/%s", b->path, b->mdir_best); + mb_fd = open(path, O_RDONLY); + if (mb_fd < 0) + { + debug("[open failed: %m] "); + prepare_snippets(b, sender, subject); + return; + } + mb_seekable = 1; + mb_reset(0); + + while (read_hdr_field(buf) > 0) + { + if (!strncasecmp(buf, "From:", 5)) + strcpy(sender, buf+5); + else if (!strncasecmp(buf, "Subject:", 8)) + strcpy(subject, buf+8); + } + + close(mb_fd); + prepare_snippets(b, sender, subject); +} + +static void +scan_mdir(struct mbox *b) +{ + int dir_len = strlen(b->path); + char path[dir_len + MDIR_MAX_NAME_LEN]; + strcpy(path, b->path); + + b->total = b->new = b->flagged = 0; + b->best_time = 0; + b->best_is_new = 0; + + for (int new=0; new<2; new++) + { + strcpy(path + dir_len, (new ? "/new" : "/cur")); + DIR *d = opendir(path); + if (!d) + { + debug("[cannot open %s: %m] "); + continue; + } + struct dirent *de; + while (de = readdir(d)) + { + char *name = de->d_name; + if (name[0] == '.' || strlen(name) + 10 > MDIR_MAX_NAME_LEN) + continue; + path[dir_len + 4] = '/'; + strcpy(path + dir_len + 5, name); + + char *colon = strchr(name, ':'); + int seen = 0; + int flagged = 0; + if (colon && colon[1] == '2' && colon[2] == ',') + { + // Another comma can separate extended flags (Dovecot extension) + for (int i=3; colon[i] && colon[i] != ','; i++) + switch (colon[i]) + { + case 'S': + seen = 1; + break; + case 'F': + flagged = 1; + break; + } + } + + int is_new = new; + if (b->o.unread_is_new && !seen) + is_new = 1; + + b->total++; + if (is_new) + b->new++; + if (flagged) + b->flagged++; + if (debug_mode > 1) + debug("%s: new=%d flagged=%d seen=%d\n", path, is_new, flagged, seen); + + if (is_new || flagged) + { + // Need to pick the best message to display + struct stat st; + if (stat(path, &st) < 0) + debug("[cannot stat %s: %m] ", path); + else if (is_new > b->best_is_new || is_new == b->best_is_new && st.st_mtime > b->best_time) + { + b->best_is_new = is_new; + b->best_time = st.st_mtime; + strcpy(b->mdir_best, path + dir_len + 1); + } + } + } + closedir(d); + } + + if (b->best_time && b->o.snippets) + { + debug("best <%s> ", b->mdir_best); + mdir_get_snippet(b); + } +} + static void scan(int notify) { @@ -586,31 +781,65 @@ scan(int notify) debug("inactive\n"); continue; } - if (force_refresh) - b->force_refresh = 1; + b->force_refresh = force_refresh; if (stat(b->path, &st) < 0) { b->total = b->new = b->flagged = -1; debug("%m\n"); + continue; } - else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh) + + time_t current_mtime; + int current_size; + int is_mdir; + if (S_ISREG(st.st_mode)) + { + // Regular mailbox + is_mdir = 0; + current_mtime = st.st_mtime; + current_size = st.st_size; + debug("[mbox] "); + } + else if (S_ISDIR(st.st_mode)) + { + // Maildir + is_mdir = 1; + current_mtime = mdir_mtime(b); + current_size = 0; + debug("[mdir] "); + } + else + { + debug("neither file nor directory\n"); + continue; + } + + if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh) { b->scanning = 1; redraw_line(b->index); refresh(); - scan_mbox(b, &st); - b->last_time = st.st_mtime; - b->last_size = st.st_size; + if (is_mdir) + scan_mdir(b); + else + scan_mbox(b, &st); + b->last_time = current_mtime; + b->last_size = current_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); refresh(); + b->force_refresh = 0; + } + else if (b->display_valid_until <= last_scan_time) + { + debug("not changed, but needs redraw\n"); + redraw_line(b->index); } else debug("not changed\n"); - b->force_refresh = 0; } force_refresh = 0; @@ -631,6 +860,7 @@ static Atom osd_pty; static unsigned osd_care; #define OSD_MSG_SIZE 1024 static char osd_last_msg[OSD_MSG_SIZE]; +static time_t osd_last_time; static void x11_init(void) @@ -838,7 +1068,7 @@ rethink_osd(int notify) if (b->o.osd > 0) { p.total_new += b->new; - if (b->new && (!p.mbox || p.mbox->o.priority < b->o.priority)) + if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority)) p.mbox = b; } @@ -856,6 +1086,7 @@ rethink_osd(int notify) } else debug("OSD: No changes\n"); + osd_last_time = time(NULL); } } @@ -905,6 +1136,7 @@ redraw_line(int i) int hi = b->o.highlight; unsigned namepos = 0; unsigned namelen = strlen(b->name); + int valid = 3600; attrset(attrs[cc][hi][M_IDLE]); if (b->o.hotkey) @@ -951,11 +1183,14 @@ redraw_line(int i) attrset(attrs[cc][hi][M_NEW]); printw("%6d ", b->new); attrset(attrs[cc][hi][M_IDLE]); - int age = (last_scan_time - b->last_time); + int age = (last_scan_time - b->best_time); if (age < 0) age = 0; if (age < 3600) - printw("%2d min ", age/60); + { + printw("%2d min ", age/60); + valid = 60; + } else if (age < 86400) printw("%2d hr%c ", age/3600, (age >= 7200 ? 's' : ' ')); else @@ -994,6 +1229,7 @@ redraw_line(int i) } } } + b->display_valid_until = last_scan_time + valid; } attrset(attrs[0][0][M_IDLE]); clrtoeol(); @@ -1200,10 +1436,21 @@ next_active(int since, int step) do { struct mbox *b = mbox_array[i]; - if (b->new && b->o.priority > bestp) + if (simple_tab) { - besti = i; - bestp = b->o.priority; + if (b->new) + { + besti = i; + break; + } + } + else + { + if (b->new && b->o.priority > bestp) + { + besti = i; + bestp = b->o.priority; + } } i = (i+step) % cursor_max; } @@ -1290,6 +1537,7 @@ Options:\n\ -o \t\tSet default options for all mailboxes\n\ -p \t\tSet minimum priority to show\n\ -s =\t\tSet on-screen display options (consult OSDD docs)\n\ +-t\t\t\tLet TAB select the next mailbox with new mail, no matter what priority it has\n\ \n\ Mailbox options (set with `-o', use upper case to negate):\n\ 0-9\t\t\tSet mailbox priority (0=default)\n\ @@ -1300,6 +1548,7 @@ f\t\t\tShow flagged messages if there are no new ones\n\ h\t\t\tHide from display\n\ l\t\t\tLight a keyboard led (1-9) if running on X display\n\ m\t\t\tShow mailbox name of the sender\n\ +o\t\t\tCount old, but unread messages as new\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\ @@ -1360,6 +1609,9 @@ parse_options(char *c) case 'm': o->sender_mbox = value; break; + case 'o': + o->unread_is_new = value; + break; case 'p': o->sender_personal = value; break; @@ -1385,7 +1637,7 @@ main(int argc, char **argv) clist_init(&osd_opts); int c; - while ((c = getopt(argc, argv, "c:dim:o:p:s:")) >= 0) + while ((c = getopt(argc, argv, "c:dim:o:p:s:t")) >= 0) switch (c) { case 'c': @@ -1411,6 +1663,9 @@ main(int argc, char **argv) case 's': add_osd_opt(optarg); break; + case 't': + simple_tab = 1; + break; default: usage(); }