From 60687231eb923234756020bf9d9cccd20551fd90 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Fri, 20 May 2005 20:54:21 +0000 Subject: [PATCH 1/1] Initial revision --- Makefile | 18 ++ clists.h | 93 ++++++++ cm.c | 669 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ log | 10 + util.c | 59 +++++ util.h | 26 +++ 6 files changed, 875 insertions(+) create mode 100644 Makefile create mode 100644 clists.h create mode 100644 cm.c create mode 100644 log create mode 100644 util.c create mode 100644 util.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0c7ff67 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +DEBUG=-ggdb +CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Winline $(DEBUG) -std=gnu99 +LDFLAGS=-lncurses + +all: cm + +cm: cm.o util.o + +cm.o: cm.c clists.h util.h +util.o: util.c util.h + +clean: + rm -f `find . -name "*~" -or -name "*.[oa]" -or -name "\#*\#" -or -name TAGS -or -name core` + rm -f cm + +distclean: clean + +.PHONY: all clean distclean diff --git a/clists.h b/clists.h new file mode 100644 index 0000000..846752f --- /dev/null +++ b/clists.h @@ -0,0 +1,93 @@ +/* + * UCW Library -- Circular Linked Lists + * + * (c) 2003--2005 Martin Mares + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#ifndef _UCW_CLISTS_H +#define _UCW_CLISTS_H + +typedef struct cnode { + struct cnode *next, *prev; +} cnode; + +typedef struct clist { + struct cnode head; +} clist; + +static inline void *clist_head(clist *l) +{ + return (l->head.next != &l->head) ? l->head.next : NULL; +} + +static inline void *clist_tail(clist *l) +{ + return (l->head.prev != &l->head) ? l->head.prev : NULL; +} + +static inline void *clist_next(clist *l, cnode *n) +{ + return (n->next != &l->head) ? (void *) n->next : NULL; +} + +static inline void *clist_prev(clist *l, cnode *n) +{ + return (n->prev != &l->head) ? (void *) n->prev : NULL; +} + +static inline int clist_empty(clist *l) +{ + return (l->head.next == &l->head); +} + +#define CLIST_WALK(n,list) for(n=(void*)(list).head.next; (cnode*)(n) != &(list).head; n=(void*)((cnode*)(n))->next) +#define CLIST_WALK_DELSAFE(n,list,tmp) for(n=(void*)(list).head.next; tmp=(void*)((cnode*)(n))->next, (cnode*)(n) != &(list).head; n=(void*)tmp) +#define CLIST_FOR_EACH(type,n,list) for(type n=(void*)(list).head.next; (cnode*)(n) != &(list).head; n=(void*)((cnode*)(n))->next) +#define CLIST_FOR_EACH_DELSAFE(type,n,list,tmp) for(type n=(void*)(list).head.next; tmp=(void*)((cnode*)(n))->next, (cnode*)(n) != &(list).head; n=(void*)tmp) + +static inline void clist_insert_after(cnode *what, cnode *after) +{ + cnode *before = after->next; + what->next = before; + what->prev = after; + before->prev = what; + after->next = what; +} + +static inline void clist_insert_before(cnode *what, cnode *before) +{ + cnode *after = before->prev; + what->next = before; + what->prev = after; + before->prev = what; + after->next = what; +} + +static inline void clist_add_tail(clist *l, cnode *n) +{ + clist_insert_before(n, &l->head); +} + +static inline void clist_add_head(clist *l, cnode *n) +{ + clist_insert_after(n, &l->head); +} + +static inline void clist_remove(cnode *n) +{ + cnode *before = n->prev; + cnode *after = n->next; + before->next = after; + after->prev = before; +} + +static inline void clist_init(clist *l) +{ + cnode *head = &l->head; + head->next = head->prev = head; +} + +#endif diff --git a/cm.c b/cm.c new file mode 100644 index 0000000..8b4e3e5 --- /dev/null +++ b/cm.c @@ -0,0 +1,669 @@ +/* + * Incoming Mail Checker + * + * (c) 2005 Martin Mares + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "clists.h" + +static int check_interval = 30; +static int force_refresh; +static int lock_mboxes; +static time_t last_scan_time; +static char *run_cmd = "mutt -f %s"; + +struct mbox { + cnode n; + 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 force_refresh; +}; + +static clist mboxes, hilites, patterns; +static struct mbox **mbox_array; +static int num_mboxes; + +static void redraw_line(int i); +static void redraw_all(void); + +static char * +mbox_name(char *path) +{ + char *c = strrchr(path, '/'); + return c ? (c+1) : path; +} + +static struct mbox * +add_mbox(clist *l, char *path, char *name) +{ + struct mbox *b = xmalloc(sizeof(*b)); + bzero(b, sizeof(*b)); + b->path = xstrdup(path); + b->name = xstrdup(name); + + if (name) + { + cnode *prev = l->head.prev; + while (prev != &l->head && strcmp(((struct mbox *)prev)->name, name) > 0) + prev = prev->prev; + clist_insert_after(&b->n, prev); + } + else + clist_add_tail(l, &b->n); + + return b; +} + +static void +del_mbox(struct mbox *b) +{ + clist_remove(&b->n); + free(b->path); + free(b->name); + free(b); +} + +static struct mbox * +find_mbox(clist *l, char *path) +{ + CLIST_FOR_EACH(struct mbox *, b, *l) + if (!strcmp(b->path, path)) + return b; + return NULL; +} + +static void +add_inbox(clist *l) +{ + 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"); +} + +static int mb_fd, mb_pos; +static unsigned char mb_buf[4096], *mb_cc, *mb_end; + +static void +mb_reset(int pos) +{ + mb_cc = mb_end = mb_buf; + mb_pos = pos; +} + +static int +mb_seek(uns pos) +{ + lseek(mb_fd, pos, SEEK_SET); + mb_reset(pos); +} + +static int +mb_tell(void) +{ + return mb_pos - (mb_end - mb_cc); +} + +static int +mb_ll_get(void) +{ + int len = read(mb_fd, mb_buf, sizeof(mb_buf)); + mb_cc = mb_buf; + if (len <= 0) + { + mb_end = mb_buf; + return -1; + } + else + { + mb_end = mb_buf + len; + mb_pos += len; + return *mb_cc++; + } +} + +static inline int +mb_get(void) +{ + return (mb_cc < mb_end) ? *mb_cc++ : mb_ll_get(); +} + +static int +mb_check(const char *p, int len) +{ + while (len--) + { + if (mb_get() != *p++) + return 0; + } + return 1; +} + +static void +scan_mbox(struct mbox *b, struct stat *st) +{ + char buf[1024]; + int c; + const char from[] = "\nFrom "; + + if (!st->st_size) + { + b->total = b->new = 0; + b->last_pos = 0; + return; + } + + /* FIXME: Locking! */ + + mb_fd = open(b->path, O_RDONLY); + if (mb_fd < 0) + { + debug("[open failed: %m] "); + b->total = b->new = -1; + return; + } + mb_reset(0); + + int incremental = 0; + if (b->last_size && b->last_pos && st->st_size > b->last_size && !b->force_refresh) + { + mb_seek(b->last_pos); + if (mb_check(from, 6)) + { + debug("[incremental] "); + incremental = 1; + } + else + { + debug("[incremental failed] "); + mb_seek(0); + } + } + if (!incremental) + { + if (!mb_check(from+1, 5)) + { + debug("[inconsistent] "); + b->total = b->new = -1; + goto done; + } + b->total = b->new = 0; + b->last_total = b->last_new = 0; + } + else + { + b->total = b->last_total; + b->new = b->last_new; + } + + for(;;) + { + b->last_pos = mb_tell() - 5; + if (b->last_pos) + b->last_pos--; // last_pos should be the previous \n character + b->last_total = b->total; + b->last_new = b->new; + while ((c = mb_get()) >= 0 && c != '\n') + ; + + int new = 1; + for (;;) + { + uns i = 0; + for (;;) + { + c = mb_get(); + if (c < 0) + { + debug("[truncated] "); + goto done; + } + if (c == '\n') + break; + if (c == '\r') + continue; + if (i < sizeof(buf) - 1) + buf[i++] = c; + } + buf[i] = 0; + if (!buf[0]) + break; + if (!strncasecmp(buf, "Status:", 7)) + new = 0; + } + + b->total++; + if (new) + b->new++; + + int ct = 1; + while (from[ct]) + { + c = mb_get(); + if (c < 0) + goto done; + if (c != from[ct++]) + ct = (c == '\n'); + } + } + + done: + close(mb_fd); +} + +static void +scan(void) +{ + debug("Searching for mailboxes...\n"); + int changed = 0; + CLIST_FOR_EACH(struct mbox *, p, patterns) + { + glob_t g; + int err = glob(p->path, GLOB_ERR | GLOB_NOSORT | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &g); + if (err && err != GLOB_NOMATCH) + die("Failed to glob %s: %m", p->path); + 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); + 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; + } + 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(); + } + + debug("Scanning mailboxes...\n"); + CLIST_FOR_EACH(struct mbox *, b, mboxes) + { + struct stat st; + debug("%s: ", b->name); + if (force_refresh) + b->force_refresh = 1; + if (stat(b->path, &st) < 0) + { + b->total = b->new = -1; + debug("%m\n"); + } + else if (!b->last_time || st.st_mtime != b->last_time || st.st_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; + debug("%d %d (stopped at %d of %d)\n", b->total, b->new, b->last_pos, b->last_size); + + b->scanning = 0; + redraw_line(b->index); + refresh(); + } + else + debug("not changed\n"); + b->force_refresh = 0; + } + force_refresh = 0; + + debug("Scan finished\n"); + last_scan_time = time(NULL); +} + +static int cursor_at, cursor_max; + +enum { + M_IDLE, + M_SCAN, + M_NEW, + M_BAD, + M_MAX +}; +static int attrs[2][2][M_MAX]; // active, hilite, status + +static void +redraw_line(int i) +{ + move(i, 0); + if (i < cursor_max) + { + struct mbox *b = mbox_array[i]; + int cc = (cursor_at == i); + int hi = b->hilited; + + attrset(attrs[cc][hi][M_IDLE]); + if (cc) + printw("> "); + else + printw(" "); + if (b->new) + attrset(attrs[cc][hi][M_NEW]); + printw("%-20s ", b->name); + if (b->scanning < 0) + ; + else if (b->scanning) + { + attrset(attrs[cc][hi][M_SCAN]); + printw("[SCANNING]"); + } + else if (b->total < 0) + { + attrset(attrs[cc][hi][M_BAD]); + printw("BROKEN"); + } + else + { + attrset(attrs[cc][hi][M_IDLE]); + printw("%4d ", b->total); + if (b->new) + { + attrset(attrs[cc][hi][M_NEW]); + printw("%4d", b->new); + } + } + attrset(attrs[cc][hi][M_IDLE]); + } + else + attrset(attrs[0][0][M_IDLE]); + clrtoeol(); +} + +static void +redraw_all(void) +{ + cursor_max = num_mboxes; + if (cursor_max > LINES-1) + cursor_max = LINES-1; + if (cursor_at >= cursor_max) + cursor_at = cursor_max - 1; + if (cursor_at < 0) + cursor_at = 0; + + for (int i=0; i= 5) + { + init_pair(1, COLOR_YELLOW, COLOR_BLACK); + init_pair(2, COLOR_RED, COLOR_BLACK); + init_pair(3, COLOR_WHITE, COLOR_BLUE); + init_pair(4, COLOR_YELLOW, COLOR_BLUE); + init_pair(5, COLOR_RED, COLOR_BLUE); + static const int attrs_color[2][2][M_MAX] = { + [0][0] = { [M_IDLE] = 0, [M_SCAN] = COLOR_PAIR(1), [M_NEW] = COLOR_PAIR(1), [M_BAD] = COLOR_PAIR(2) }, + [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 }, + [1][0] = { [M_IDLE] = COLOR_PAIR(3), [M_SCAN] = COLOR_PAIR(4), [M_NEW] = COLOR_PAIR(4), [M_BAD] = COLOR_PAIR(5) }, + [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 }, + }; + memcpy(attrs, attrs_color, sizeof(attrs)); + } + } + clear(); +} + +static void +term_cleanup(void) +{ + endwin(); +} + +static void +scan_and_redraw(void) +{ + move(LINES-1, 0); + printw("Busy..."); + refresh(); + scan(); + redraw_all(); + refresh(); +} + +static void +move_cursor(int i) +{ + if (i >= 0 && i < cursor_max && i != cursor_at) + { + int old = cursor_at; + cursor_at = i; + redraw_line(old); + redraw_line(i); + } +} + +static void +next_active(int since) +{ + if (!cursor_max) + return; + since %= cursor_max; + for (int pass=1; pass >= 0; pass--) + { + 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); + } +} + +static void NONRET +usage(void) +{ + 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\ +"); + exit(1); +} + +int +main(int argc, char **argv) +{ + clist_init(&mboxes); + clist_init(&hilites); + clist_init(&patterns); + + int c; + while ((c = getopt(argc, argv, "c:dh:ilm:")) >= 0) + switch (c) + { + case 'c': + check_interval = atol(optarg); + if (check_interval <= 0) + usage(); + break; + case 'd': + debug_mode++; + break; + case 'h': + add_mbox(&hilites, optarg, NULL); + break; + case 'i': + add_inbox(&patterns); + break; + case 'l': + lock_mboxes = 1; + break; + case 'm': + run_cmd = optarg; + break; + default: + usage(); + } + while (optind < argc) + add_mbox(&patterns, argv[optind++], NULL); + + term_init(); + scan_and_redraw(); + next_active(0); + + int should_exit = 0; + while (!should_exit) + { + time_t now = time(NULL); + int remains = last_scan_time + check_interval - now; + if (remains <= 0 || force_refresh) + scan_and_redraw(); + else + { + remains *= 10; + halfdelay((remains > 255) ? 255 : remains); + int ch = getch(); + switch (ch) + { + case 'q': + should_exit = 1; + break; + case 'j': + case KEY_DOWN: + move_cursor(cursor_at+1); + break; + case 'k': + case KEY_UP: + move_cursor(cursor_at-1); + break; + case '0': + case KEY_HOME: + case KEY_PPAGE: + move_cursor(0); + break; + case '$': + case KEY_END: + case KEY_NPAGE: + move_cursor(cursor_max-1); + break; + case '\t': + next_active(cursor_at+1); + break; + case '\r': + case '\n': + if (cursor_at < cursor_max) + { + struct mbox *b = mbox_array[cursor_at]; + char cmd[strlen(run_cmd) + strlen(b->path) + 16]; + sprintf(cmd, run_cmd, b->path); + term_cleanup(); + system(cmd); + term_init(); + redraw_all(); + refresh(); + b->force_refresh = 1; + scan_and_redraw(); + } + break; + case 'r' & 0x1f: + force_refresh = 1; + break; + } + refresh(); + } + } + + term_cleanup(); + return 0; +} diff --git a/log b/log new file mode 100644 index 0000000..ecc2b07 --- /dev/null +++ b/log @@ -0,0 +1,10 @@ +./cm: invalid option -- - +Usage: cm [] + +Options: +-c Scan mailboxes every seconds (default is 30) +-d Log debug messages to stderr +-h Highlight and prefer the specified mailbox +-i Include user's INBOX +-l Lock mailboxes when scanning +-m Command to run on the selected mailbox, %s gets replaced by mailbox path diff --git a/util.c b/util.c new file mode 100644 index 0000000..e211755 --- /dev/null +++ b/util.c @@ -0,0 +1,59 @@ +/* + * Utility Functions + * + * (c) 2005 Martin Mares + */ + +#include +#include +#include +#include + +#include "util.h" + +int debug_mode; + +void NONRET +die(char *c, ...) +{ + va_list args; + va_start(args, c); + fprintf(stderr, "cm: "); + vfprintf(stderr, c, args); + fputc('\n', stderr); + va_end(args); + exit(1); +} + +void +debug(char *c, ...) +{ + if (!debug_mode) + return; + + va_list args; + va_start(args, c); + vfprintf(stderr, c, args); + fflush(stderr); + va_end(args); +} + +void * +xmalloc(uns size) +{ + void *buf = malloc(size); + if (!buf) + die("Unable to allocate %d bytes of memory", size); + return buf; +} + +char * +xstrdup(char *s) +{ + if (!s) + return s; + uns len = strlen(s) + 1; + char *new = xmalloc(len); + memcpy(new, s, len); + return new; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..47b19ad --- /dev/null +++ b/util.h @@ -0,0 +1,26 @@ +/* + * Various Utility Functions (extracted from the UCW Library) + * + * (c) 2005 Martin Mares + */ + +#ifdef __GNUC__ +#define NONRET __attribute__((noreturn)) +#define UNUSED __attribute__((unused)) +#define likely(x) __builtin_expect((x),1) +#define unlikely(x) __builtin_expect((x),0) +#else +#define NONRET +#define UNUSED +#define likely(x) (x) +#define ulikely(x) (x) +#endif + +typedef unsigned int uns; + +extern int debug_mode; + +void NONRET die(char *x, ...); +void debug(char *x, ...); +void *xmalloc(uns size); +char *xstrdup(char *s); -- 2.39.2