aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/faces
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-10-31 14:47:39 +0000
committerrsc <devnull@localhost>2005-10-31 14:47:39 +0000
commitb330c942b468ab82fd8853590145187e859258cb (patch)
treecb66b302b657bf0e966695fa0ab1d9596da234a0 /src/cmd/faces
parent663ddde9d07417ab51239c0c4305708a1a319c62 (diff)
downloadplan9port-b330c942b468ab82fd8853590145187e859258cb.tar.gz
plan9port-b330c942b468ab82fd8853590145187e859258cb.tar.bz2
plan9port-b330c942b468ab82fd8853590145187e859258cb.zip
initial faces (John Cummings)
Diffstat (limited to 'src/cmd/faces')
-rw-r--r--src/cmd/faces/dblook.c30
-rw-r--r--src/cmd/faces/facedb.c562
-rw-r--r--src/cmd/faces/faces.h68
-rw-r--r--src/cmd/faces/main.c783
-rw-r--r--src/cmd/faces/mkfile26
-rw-r--r--src/cmd/faces/plumb.c398
-rw-r--r--src/cmd/faces/util.c42
7 files changed, 1909 insertions, 0 deletions
diff --git a/src/cmd/faces/dblook.c b/src/cmd/faces/dblook.c
new file mode 100644
index 00000000..b5c023f5
--- /dev/null
+++ b/src/cmd/faces/dblook.c
@@ -0,0 +1,30 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <plumb.h>
+#include <regexp.h>
+#include <bio.h>
+#include <9pclient.h>
+#include <thread.h>
+#include "faces.h"
+
+void
+threadmain(int argc, char **argv)
+{
+ Face f;
+ char *q;
+
+ if(argc != 3){
+ fprint(2, "usage: dblook name domain\n");
+ threadexitsall("usage");
+ }
+
+ q = findfile(&f, argv[2], argv[1]);
+ print("%s\n", q);
+}
+
+void
+killall(char *s)
+{
+ USED(s);
+}
diff --git a/src/cmd/faces/facedb.c b/src/cmd/faces/facedb.c
new file mode 100644
index 00000000..0c4f2d79
--- /dev/null
+++ b/src/cmd/faces/facedb.c
@@ -0,0 +1,562 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <plumb.h>
+#include <regexp.h>
+#include <bio.h>
+#include <9pclient.h>
+#include "faces.h"
+
+enum /* number of deleted faces to cache */
+{
+ Nsave = 20,
+};
+
+static Facefile *facefiles;
+static int nsaved;
+static char *facedom;
+
+/*
+ * Loading the files is slow enough on a dial-up line to be worth this trouble
+ */
+typedef struct Readcache Readcache;
+struct Readcache {
+ char *file;
+ char *data;
+ long mtime;
+ long rdtime;
+ Readcache *next;
+};
+
+static Readcache *rcache;
+
+ulong
+dirlen(char *s)
+{
+ Dir *d;
+ ulong len;
+
+ d = dirstat(s);
+ if(d == nil)
+ return 0;
+ len = d->length;
+ free(d);
+ return len;
+}
+
+ulong
+fsdirlen(CFsys *fs,char *s)
+{
+ Dir *d;
+ ulong len;
+
+ d = fsdirstat(fs,s);
+ if(d == nil)
+ return 0;
+ len = d->length;
+ free(d);
+ return len;
+}
+
+ulong
+dirmtime(char *s)
+{
+ Dir *d;
+ ulong t;
+
+ d = dirstat(s);
+ if(d == nil)
+ return 0;
+ t = d->mtime;
+ free(d);
+ return t;
+}
+
+static char*
+doreadfile(char *s)
+{
+ char *p;
+ int fd, n;
+ ulong len;
+
+ len = dirlen(s);
+ if(len == 0)
+ return nil;
+
+ p = malloc(len+1);
+ if(p == nil)
+ return nil;
+
+ if((fd = open(s, OREAD)) < 0
+ || (n = readn(fd, p, len)) < 0) {
+ close(fd);
+ free(p);
+ return nil;
+ }
+
+ p[n] = '\0';
+ return p;
+}
+
+static char*
+readfile(char *s)
+{
+ Readcache *r, **l;
+ char *p;
+ ulong mtime;
+
+ for(l=&rcache, r=*l; r; l=&r->next, r=*l) {
+ if(strcmp(r->file, s) != 0)
+ continue;
+
+ /*
+ * if it's less than 30 seconds since we read it, or it
+ * hasn't changed, send back our copy
+ */
+ if(time(0) - r->rdtime < 30)
+ return strdup(r->data);
+ if(dirmtime(s) == r->mtime) {
+ r->rdtime = time(0);
+ return strdup(r->data);
+ }
+
+ /* out of date, remove this and fall out of loop */
+ *l = r->next;
+ free(r->file);
+ free(r->data);
+ free(r);
+ break;
+ }
+
+ /* add to cache */
+ mtime = dirmtime(s);
+ if(mtime == 0)
+ return nil;
+
+ if((p = doreadfile(s)) == nil)
+ return nil;
+
+ r = malloc(sizeof(*r));
+ if(r == nil)
+ return nil;
+ r->mtime = mtime;
+ r->file = estrdup(s);
+ r->data = p;
+ r->rdtime = time(0);
+ r->next = rcache;
+ rcache = r;
+ return strdup(r->data);
+}
+
+
+static char*
+translatedomain(char *dom)
+{
+ static char buf[200];
+ char *p, *ep, *q, *nextp, *file;
+ char *bbuf, *ebuf;
+ Reprog *exp;
+
+ if(dom == nil || *dom == 0)
+ return nil;
+
+ if((file = readfile(unsharp("#9/lib/face/.machinelist"))) == nil)
+ return dom;
+
+ for(p=file; p; p=nextp) {
+ if(nextp = strchr(p, '\n'))
+ *nextp++ = '\0';
+
+ if(*p == '#' || (q = strpbrk(p, " \t")) == nil || q-p > sizeof(buf)-2)
+ continue;
+
+ bbuf = buf+1;
+ ebuf = buf+(1+(q-p));
+ strncpy(bbuf, p, ebuf-bbuf);
+ *ebuf = 0;
+ if(*bbuf != '^')
+ *--bbuf = '^';
+ if(ebuf[-1] != '$') {
+ *ebuf++ = '$';
+ *ebuf = 0;
+ }
+
+ if((exp = regcomp(bbuf)) == nil){
+ fprint(2, "bad regexp in machinelist: %s\n", bbuf);
+ killall("regexp");
+ }
+
+ if(regexec(exp, dom, 0, 0)){
+ free(exp);
+ ep = p+strlen(p);
+ q += strspn(q, " \t");
+ if(ep-q+2 > sizeof buf) {
+ fprint(2, "huge replacement in machinelist: %.*s\n", utfnlen(q, ep-q), q);
+ exits("bad big replacement");
+ }
+ strncpy(buf, q, ep-q);
+ ebuf = buf+(ep-q);
+ *ebuf = 0;
+ while(ebuf > buf && (ebuf[-1] == ' ' || ebuf[-1] == '\t'))
+ *--ebuf = 0;
+ free(file);
+ return buf;
+ }
+ free(exp);
+ }
+ free(file);
+
+ return dom;
+}
+
+static char*
+tryfindpicture_user(char *dom, char *user, int depth)
+{
+ static char buf[200];
+ char *p, *q, *nextp, *file, *usr;
+ usr = getuser();
+
+ sprint(buf, "/usr/%s/lib/face/48x48x%d/.dict", usr, depth);
+ if((file = readfile(buf)) == nil)
+ return nil;
+
+ snprint(buf, sizeof buf, "%s/%s", dom, user);
+
+ for(p=file; p; p=nextp) {
+ if(nextp = strchr(p, '\n'))
+ *nextp++ = '\0';
+
+ if(*p == '#' || (q = strpbrk(p, " \t")) == nil)
+ continue;
+ *q++ = 0;
+
+ if(strcmp(buf, p) == 0) {
+ q += strspn(q, " \t");
+ q = buf+snprint(buf, sizeof buf, "/usr/%s/lib/face/48x48x%d/%s", usr, depth, q);
+ while(q > buf && (q[-1] == ' ' || q[-1] == '\t'))
+ *--q = 0;
+ free(file);
+ return buf;
+ }
+ }
+ free(file);
+ return nil;
+}
+
+static char*
+tryfindpicture_global(char *dom, char *user, int depth)
+{
+ static char buf[200];
+ char *p, *q, *nextp, *file;
+
+ sprint(buf, "#9/lib/face/48x48x%d/.dict", depth);
+ if((file = readfile(unsharp(buf))) == nil)
+ return nil;
+
+ snprint(buf, sizeof buf, "%s/%s", dom, user);
+
+ for(p=file; p; p=nextp) {
+ if(nextp = strchr(p, '\n'))
+ *nextp++ = '\0';
+
+ if(*p == '#' || (q = strpbrk(p, " \t")) == nil)
+ continue;
+ *q++ = 0;
+
+ if(strcmp(buf, p) == 0) {
+ q += strspn(q, " \t");
+ q = buf+snprint(buf, sizeof buf, "#9/lib/face/48x48x%d/%s", depth, q);
+ while(q > buf && (q[-1] == ' ' || q[-1] == '\t'))
+ *--q = 0;
+ free(file);
+ return unsharp(buf);
+ }
+ }
+ free(file);
+ return nil;
+}
+
+static char*
+tryfindpicture(char *dom, char *user, int depth)
+{
+ char* result;
+
+ if((result = tryfindpicture_user(dom, user, depth)) != nil)
+ return result;
+
+ return tryfindpicture_global(dom, user, depth);
+}
+
+static char*
+tryfindfile(char *dom, char *user, int depth)
+{
+ char *p, *q;
+
+ for(;;){
+ for(p=dom; p; (p=strchr(p, '.')) && p++)
+ if(q = tryfindpicture(p, user, depth))
+ return q;
+ depth >>= 1;
+ if(depth == 0)
+ break;
+ }
+ return nil;
+}
+
+char*
+findfile(Face *f, char *dom, char *user)
+{
+ char *p;
+ int depth;
+
+ if(facedom == nil){
+ facedom = getenv("facedom");
+ if(facedom == nil)
+ facedom = DEFAULT;
+ }
+
+ dom = translatedomain(dom);
+ if(dom == nil)
+ dom = facedom;
+
+ if(screen == nil)
+ depth = 8;
+ else
+ depth = screen->depth;
+
+ if(depth > 8)
+ depth = 8;
+
+ f->unknown = 0;
+ if(p = tryfindfile(dom, user, depth))
+ return p;
+ f->unknown = 1;
+ p = tryfindfile(dom, "unknown", depth);
+ if(p != nil || strcmp(dom, facedom)==0)
+ return p;
+ return tryfindfile("unknown", "unknown", depth);
+}
+
+static
+void
+clearsaved(void)
+{
+ Facefile *f, *next, **lf;
+
+ lf = &facefiles;
+ for(f=facefiles; f!=nil; f=next){
+ next = f->next;
+ if(f->ref > 0){
+ *lf = f;
+ lf = &(f->next);
+ continue;
+ }
+ if(f->image != display->black && f->image != display->white)
+ freeimage(f->image);
+ free(f->file);
+ free(f);
+ }
+ *lf = nil;
+ nsaved = 0;
+}
+
+void
+freefacefile(Facefile *f)
+{
+ if(f==nil || f->ref-->1)
+ return;
+ if(++nsaved > Nsave)
+ clearsaved();
+}
+
+static Image*
+myallocimage(ulong chan)
+{
+ Image *img;
+ img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
+ if(img == nil){
+ clearsaved();
+ img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
+ if(img == nil)
+ return nil;
+ }
+ return img;
+}
+
+
+static Image*
+readbit(int fd, ulong chan)
+{
+ char buf[4096], hx[4], *p;
+ uchar data[Facesize*Facesize]; /* more than enough */
+ int nhx, i, n, ndata, nbit;
+ Image *img;
+
+ n = readn(fd, buf, sizeof buf);
+ if(n <= 0)
+ return nil;
+ if(n >= sizeof buf)
+ n = sizeof(buf)-1;
+ buf[n] = '\0';
+
+ n = 0;
+ nhx = 0;
+ nbit = chantodepth(chan);
+ ndata = (Facesize*Facesize*nbit)/8;
+ p = buf;
+ while(n < ndata) {
+ p = strpbrk(p+1, "0123456789abcdefABCDEF");
+ if(p == nil)
+ break;
+ if(p[0] == '0' && p[1] == 'x')
+ continue;
+
+ hx[nhx] = *p;
+ if(++nhx == 2) {
+ hx[nhx] = 0;
+ i = strtoul(hx, 0, 16);
+ data[n++] = i;
+ nhx = 0;
+ }
+ }
+ if(n < ndata)
+ return allocimage(display, Rect(0,0,Facesize,Facesize), CMAP8, 0, 0x88888888);
+
+ img = myallocimage(chan);
+ if(img == nil)
+ return nil;
+ loadimage(img, img->r, data, ndata);
+ return img;
+}
+
+static Facefile*
+readface(char *fn)
+{
+ int x, y, fd;
+ uchar bits;
+ uchar *p;
+ Image *mask;
+ Image *face;
+ char buf[16];
+ uchar data[Facesize*Facesize];
+ uchar mdata[(Facesize*Facesize)/8];
+ Facefile *f;
+ Dir *d;
+
+ for(f=facefiles; f!=nil; f=f->next){
+ if(strcmp(fn, f->file) == 0){
+ if(f->image == nil)
+ break;
+ if(time(0) - f->rdtime >= 30) {
+ if(dirmtime(fn) != f->mtime){
+ f = nil;
+ break;
+ }
+ f->rdtime = time(0);
+ }
+ f->ref++;
+ return f;
+ }
+ }
+
+ if((fd = open(fn, OREAD)) < 0)
+ return nil;
+
+ if(readn(fd, buf, sizeof buf) != sizeof buf){
+ close(fd);
+ return nil;
+ }
+
+ seek(fd, 0, 0);
+
+ mask = nil;
+ if(buf[0] == '0' && buf[1] == 'x'){
+ /* greyscale faces are just masks that we draw black through! */
+ if(buf[2+8] == ',') /* ldepth 1 */
+ mask = readbit(fd, GREY2);
+ else
+ mask = readbit(fd, GREY1);
+ face = display->black;
+ }else{
+ face = readimage(display, fd, 0);
+ if(face == nil)
+ goto Done;
+ else if(face->chan == GREY4 || face->chan == GREY8){ /* greyscale: use inversion as mask */
+ mask = myallocimage(face->chan);
+ /* okay if mask is nil: that will copy the image white background and all */
+ if(mask == nil)
+ goto Done;
+
+ /* invert greyscale image */
+ draw(mask, mask->r, display->white, nil, ZP);
+ gendraw(mask, mask->r, display->black, ZP, face, face->r.min);
+ freeimage(face);
+ face = display->black;
+ }else if(face->depth == 8){ /* snarf the bytes back and do a fill. */
+ mask = myallocimage(GREY1);
+ if(mask == nil)
+ goto Done;
+ if(unloadimage(face, face->r, data, Facesize*Facesize) != Facesize*Facesize){
+ freeimage(mask);
+ goto Done;
+ }
+ bits = 0;
+ p = mdata;
+ for(y=0; y<Facesize; y++){
+ for(x=0; x<Facesize; x++){
+ bits <<= 1;
+ if(data[Facesize*y+x] != 0xFF)
+ bits |= 1;
+ if((x&7) == 7)
+ *p++ = bits&0xFF;
+ }
+ }
+ if(loadimage(mask, mask->r, mdata, sizeof mdata) != sizeof mdata){
+ freeimage(mask);
+ goto Done;
+ }
+ }
+ }
+
+Done:
+ /* always add at beginning of list, so updated files don't collide in cache */
+ if(f == nil){
+ f = emalloc(sizeof(Facefile));
+ f->file = estrdup(fn);
+ d = dirfstat(fd);
+ if(d != nil){
+ f->mtime = d->mtime;
+ free(d);
+ }
+ f->next = facefiles;
+ facefiles = f;
+ }
+ f->ref++;
+ f->image = face;
+ f->mask = mask;
+ f->rdtime = time(0);
+ close(fd);
+ return f;
+}
+
+void
+findbit(Face *f)
+{
+ char *fn;
+
+ fn = findfile(f, f->str[Sdomain], f->str[Suser]);
+ if(fn) {
+ if(strstr(fn, "unknown"))
+ f->unknown = 1;
+ f->file = readface(fn);
+ }
+ if(f->file){
+ f->bit = f->file->image;
+ f->mask = f->file->mask;
+ }else{
+ /* if returns nil, this is still ok: draw(nil) works */
+ f->bit = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow);
+ replclipr(f->bit, 1, Rect(0, 0, Facesize, Facesize));
+ f->mask = nil;
+ }
+}
diff --git a/src/cmd/faces/faces.h b/src/cmd/faces/faces.h
new file mode 100644
index 00000000..7974a8b3
--- /dev/null
+++ b/src/cmd/faces/faces.h
@@ -0,0 +1,68 @@
+enum /* face strings */
+{
+ Suser,
+ Sdomain,
+ Sshow,
+ Sdigest,
+ Nstring
+};
+
+enum
+{
+ Facesize = 48,
+};
+
+typedef struct Face Face;
+typedef struct Facefile Facefile;
+
+struct Face
+{
+ Image *bit; /* unless there's an error, this is file->image */
+ Image *mask; /* unless there's an error, this is file->mask */
+ char *str[Nstring];
+ int recent;
+ ulong time;
+ Tm tm;
+ int unknown;
+ Facefile *file;
+};
+
+/*
+ * Loading the files is slow enough on a dial-up line to be worth this trouble
+ */
+struct Facefile
+{
+ Image *image;
+ Image *mask;
+ ulong mtime;
+ ulong rdtime;
+ int ref;
+ char *file;
+ Facefile *next;
+};
+
+extern char date[];
+extern char *maildir;
+extern char **maildirs;
+extern int nmaildirs;
+extern CFsys *upasfs;
+
+Face* nextface(void);
+void findbit(Face*);
+void freeface(Face*);
+void initplumb(void);
+void killall(char*);
+void showmail(Face*);
+void delete(char*, char*);
+void freefacefile(Facefile*);
+Face* dirface(char*, char*);
+void resized(void);
+int alreadyseen(char*);
+ulong dirlen(char*);
+ulong fsdirlen(CFsys*, char*);
+
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+char *estrdup(char*);
+char *findfile(Face*, char*, char*);
+void addmaildir(char*);
diff --git a/src/cmd/faces/main.c b/src/cmd/faces/main.c
new file mode 100644
index 00000000..4be56230
--- /dev/null
+++ b/src/cmd/faces/main.c
@@ -0,0 +1,783 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <plumb.h>
+#include <regexp.h>
+//jpc #include <event.h> /* for support routines only */
+#include <bio.h>
+#include <thread.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <9pclient.h>
+#include "faces.h"
+
+int history = 0; /* use old interface, showing history of mailbox rather than current state */
+int initload = 0; /* initialize program with contents of mail box */
+
+enum
+{
+ Facesep = 6, /* must be even to avoid damaging background stipple */
+ Infolines = 9,
+
+ HhmmTime = 18*60*60, /* max age of face to display hh:mm time */
+};
+
+enum
+{
+ Mainp,
+ Timep,
+ Mousep,
+ NPROC
+};
+
+int pids[NPROC];
+char *procnames[] = {
+ "main",
+ "time",
+ "mouse"
+};
+
+Rectangle leftright = {0, 0, 20, 15};
+
+uchar leftdata[] = {
+ 0x00, 0x80, 0x00, 0x01, 0x80, 0x00, 0x03, 0x80,
+ 0x00, 0x07, 0x80, 0x00, 0x0f, 0x00, 0x00, 0x1f,
+ 0xff, 0xf0, 0x3f, 0xff, 0xf0, 0xff, 0xff, 0xf0,
+ 0x3f, 0xff, 0xf0, 0x1f, 0xff, 0xf0, 0x0f, 0x00,
+ 0x00, 0x07, 0x80, 0x00, 0x03, 0x80, 0x00, 0x01,
+ 0x80, 0x00, 0x00, 0x80, 0x00
+};
+
+uchar rightdata[] = {
+ 0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1c,
+ 0x00, 0x00, 0x1e, 0x00, 0x00, 0x0f, 0x00, 0xff,
+ 0xff, 0x80, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xf0,
+ 0xff, 0xff, 0xc0, 0xff, 0xff, 0x80, 0x00, 0x0f,
+ 0x00, 0x00, 0x1e, 0x00, 0x00, 0x1c, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x10, 0x00
+};
+
+CFsys *upasfs;
+Mousectl *mousectl;
+Image *blue; /* full arrow */
+Image *bgrnd; /* pale blue background color */
+Image *left; /* left-pointing arrow mask */
+Image *right; /* right-pointing arrow mask */
+Font *tinyfont;
+Font *mediumfont;
+Font *datefont;
+int first, last; /* first and last visible face; last is first invisible */
+int nfaces;
+int mousefd;
+int nacross;
+int ndown;
+
+char date[64];
+Face **faces;
+char *maildir = "/mail/fs/mbox";
+ulong now;
+
+Point datep = { 8, 6 };
+Point facep = { 8, 6+0+4 }; /* 0 updated to datefont->height in init() */
+Point enddate; /* where date ends on display; used to place arrows */
+Rectangle leftr; /* location of left arrow on display */
+Rectangle rightr; /* location of right arrow on display */
+void updatetimes(void);
+
+void
+setdate(void)
+{
+ now = time(nil);
+ strcpy(date, ctime(now));
+ date[4+4+3+5] = '\0'; /* change from Thu Jul 22 14:28:43 EDT 1999\n to Thu Jul 22 14:28 */
+}
+
+void
+init(void)
+{
+#if 0
+ mousefd = open("/dev/mouse", OREAD);
+ if(mousefd < 0){
+ fprint(2, "faces: can't open mouse: %r\n");
+ threadexitsall("mouse");
+ }
+#endif
+ upasfs = nsmount("upasfs",nil);
+ mousectl = initmouse(nil,screen);
+ initplumb();
+
+ /* make background color */
+ bgrnd = allocimagemix(display, DPalebluegreen, DWhite);
+ blue = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x008888FF); /* blue-green */
+ left = allocimage(display, leftright, GREY1, 0, DWhite);
+ right = allocimage(display, leftright, GREY1, 0, DWhite);
+ if(bgrnd==nil || blue==nil || left==nil || right==nil){
+ fprint(2, "faces: can't create images: %r\n");
+ threadexitsall("image");
+ }
+
+ loadimage(left, leftright, leftdata, sizeof leftdata);
+ loadimage(right, leftright, rightdata, sizeof rightdata);
+
+ /* initialize little fonts */
+ tinyfont = openfont(display, "/lib/font/bit/misc/ascii.5x7.font");
+ if(tinyfont == nil)
+ tinyfont = font;
+ mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font");
+ if(mediumfont == nil)
+ mediumfont = font;
+ datefont = font;
+
+ facep.y += datefont->height;
+ if(datefont->height & 1) /* stipple parity */
+ facep.y++;
+ faces = nil;
+}
+
+void
+drawtime(void)
+{
+ Rectangle r;
+
+ r.min = addpt(screen->r.min, datep);
+ if(eqpt(enddate, ZP)){
+ enddate = r.min;
+ enddate.x += stringwidth(datefont, "Wed May 30 22:54"); /* nice wide string */
+ enddate.x += Facesep; /* for safety */
+ }
+ r.max.x = enddate.x;
+ r.max.y = enddate.y+datefont->height;
+ draw(screen, r, bgrnd, nil, ZP);
+ string(screen, r.min, display->black, ZP, datefont, date);
+}
+
+void
+timeproc(void *dummy)
+{
+ for(;;){
+ lockdisplay(display);
+ drawtime();
+ updatetimes();
+ flushimage(display, 1);
+ unlockdisplay(display);
+ sleep(60000);
+ setdate();
+ }
+}
+
+int
+alreadyseen(char *digest)
+{
+ int i;
+ Face *f;
+
+ if(!digest)
+ return 0;
+
+ /* can do accurate check */
+ for(i=0; i<nfaces; i++){
+ f = faces[i];
+ if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest])==0)
+ return 1;
+ }
+ return 0;
+}
+
+int
+torune(Rune *r, char *s, int nr)
+{
+ int i;
+
+ for(i=0; i<nr-1 && *s!='\0'; i++)
+ s += chartorune(r+i, s);
+ r[i] = L'\0';
+ return i;
+}
+
+void
+center(Font *f, Point p, char *s, Image *color)
+{
+ int i, n, dx;
+ Rune rbuf[32];
+ char sbuf[32*UTFmax+1];
+
+ dx = stringwidth(f, s);
+ if(dx > Facesize){
+ n = torune(rbuf, s, nelem(rbuf));
+ for(i=0; i<n; i++){
+ dx = runestringnwidth(f, rbuf, i+1);
+ if(dx > Facesize)
+ break;
+ }
+ sprint(sbuf, "%.*S", i, rbuf);
+ s = sbuf;
+ dx = stringwidth(f, s);
+ }
+ p.x += (Facesize-dx)/2;
+ string(screen, p, color, ZP, f, s);
+}
+
+Rectangle
+facerect(int index) /* index is geometric; 0 is always upper left face */
+{
+ Rectangle r;
+ int x, y;
+
+ x = index % nacross;
+ y = index / nacross;
+ r.min = addpt(screen->r.min, facep);
+ r.min.x += x*(Facesize+Facesep);
+ r.min.y += y*(Facesize+Facesep+2*mediumfont->height);
+ r.max = addpt(r.min, Pt(Facesize, Facesize));
+ r.max.y += 2*mediumfont->height;
+ /* simple fix to avoid drawing off screen, allowing customers to use position */
+ if(index<0 || index>=nacross*ndown)
+ r.max.x = r.min.x;
+ return r;
+}
+
+static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec";
+char*
+facetime(Face *f, int *recent)
+{
+ static char buf[30];
+
+ if((long)(now - f->time) > HhmmTime){
+ *recent = 0;
+ sprint(buf, "%.3s %2d", mon+3*f->tm.mon, f->tm.mday);
+ return buf;
+ }else{
+ *recent = 1;
+ sprint(buf, "%02d:%02d", f->tm.hour, f->tm.min);
+ return buf;
+ }
+}
+
+void
+drawface(Face *f, int i)
+{
+ char *tstr;
+ Rectangle r;
+ Point p;
+
+ if(f == nil)
+ return;
+ if(i<first || i>=last)
+ return;
+ r = facerect(i-first);
+ draw(screen, r, bgrnd, nil, ZP);
+ draw(screen, r, f->bit, f->mask, ZP);
+ r.min.y += Facesize;
+ center(mediumfont, r.min, f->str[Suser], display->black);
+ r.min.y += mediumfont->height;
+ tstr = facetime(f, &f->recent);
+ center(mediumfont, r.min, tstr, display->black);
+ if(f->unknown){
+ r.min.y -= mediumfont->height + tinyfont->height + 2;
+ for(p.x=-1; p.x<=1; p.x++)
+ for(p.y=-1; p.y<=1; p.y++)
+ center(tinyfont, addpt(r.min, p), f->str[Sdomain], display->white);
+ center(tinyfont, r.min, f->str[Sdomain], display->black);
+ }
+}
+
+void
+updatetimes(void)
+{
+ int i;
+ Face *f;
+
+ for(i=0; i<nfaces; i++){
+ f = faces[i];
+ if(f == nil)
+ continue;
+ if(((long)(now - f->time) <= HhmmTime) != f->recent)
+ drawface(f, i);
+ }
+}
+
+void
+setlast(void)
+{
+ last = first+nacross*ndown;
+ if(last > nfaces)
+ last = nfaces;
+}
+
+void
+drawarrows(void)
+{
+ Point p;
+
+ p = enddate;
+ p.x += Facesep;
+ if(p.x & 1)
+ p.x++; /* align background texture */
+ leftr = rectaddpt(leftright, p);
+ p.x += Dx(leftright) + Facesep;
+ rightr = rectaddpt(leftright, p);
+ draw(screen, leftr, first>0? blue : bgrnd, left, leftright.min);
+ draw(screen, rightr, last<nfaces? blue : bgrnd, right, leftright.min);
+}
+
+void
+addface(Face *f) /* always adds at 0 */
+{
+ Face **ofaces;
+ Rectangle r0, r1, r;
+ int y, nx, ny;
+
+ if(f == nil)
+ return;
+ lockdisplay(display);
+ if(first != 0){
+ first = 0;
+ resized();
+ }
+ findbit(f);
+
+ nx = nacross;
+ ny = (nfaces+(nx-1)) / nx;
+
+ for(y=ny; y>=0; y--){
+ /* move them along */
+ r0 = facerect(y*nx+0);
+ r1 = facerect(y*nx+1);
+ r = r1;
+ r.max.x = r.min.x + (nx - 1)*(Facesize+Facesep);
+ draw(screen, r, screen, nil, r0.min);
+ /* copy one down from row above */
+ if(y != 0){
+ r = facerect((y-1)*nx+nx-1);
+ draw(screen, r0, screen, nil, r.min);
+ }
+ }
+
+ ofaces = faces;
+ faces = emalloc((nfaces+1)*sizeof(Face*));
+ memmove(faces+1, ofaces, nfaces*(sizeof(Face*)));
+ free(ofaces);
+ nfaces++;
+ setlast();
+ drawarrows();
+ faces[0] = f;
+ drawface(f, 0);
+ flushimage(display, 1);
+ unlockdisplay(display);
+}
+
+#if 0
+void
+loadmboxfaces(char *maildir)
+{
+ int dirfd;
+ Dir *d;
+ int i, n;
+
+ dirfd = open(maildir, OREAD);
+ if(dirfd >= 0){
+ chdir(maildir);
+ while((n = dirread(dirfd, &d)) > 0){
+ for(i=0; i<n; i++)
+ addface(dirface(maildir, d[i].name));
+ free(d);
+ }
+ close(dirfd);
+ }
+}
+#endif
+
+void
+loadmboxfaces(char *maildir)
+{
+ CFid *dirfd;
+ Dir *d;
+ int i, n;
+
+ dirfd = fsopen(upasfs,maildir, OREAD);
+ if(dirfd != nil){
+ //jpc chdir(maildir);
+ while((n = fsdirread(dirfd, &d)) > 0){
+ for(i=0; i<n; i++) {
+ addface(dirface(maildir, d[i].name));
+ }
+ free(d);
+ }
+ fsclose(dirfd);
+ }
+ else {
+ error("cannot open %s: %r",maildir);
+ }
+}
+
+void
+freeface(Face *f)
+{
+ int i;
+
+ if(f->file!=nil && f->bit!=f->file->image)
+ freeimage(f->bit);
+ freefacefile(f->file);
+ for(i=0; i<Nstring; i++)
+ free(f->str[i]);
+ free(f);
+}
+
+void
+delface(int j)
+{
+ Rectangle r0, r1, r;
+ int nx, ny, x, y;
+
+ if(j < first)
+ first--;
+ else if(j < last){
+ nx = nacross;
+ ny = (nfaces+(nx-1)) / nx;
+ x = (j-first)%nx;
+ for(y=(j-first)/nx; y<ny; y++){
+ if(x != nx-1){
+ /* move them along */
+ r0 = facerect(y*nx+x);
+ r1 = facerect(y*nx+x+1);
+ r = r0;
+ r.max.x = r.min.x + (nx - x - 1)*(Facesize+Facesep);
+ draw(screen, r, screen, nil, r1.min);
+ }
+ if(y != ny-1){
+ /* copy one up from row below */
+ r = facerect((y+1)*nx);
+ draw(screen, facerect(y*nx+nx-1), screen, nil, r.min);
+ }
+ x = 0;
+ }
+ if(last < nfaces) /* first off-screen becomes visible */
+ drawface(faces[last], last-1);
+ else{
+ /* clear final spot */
+ r = facerect(last-first-1);
+ draw(screen, r, bgrnd, nil, r.min);
+ }
+ }
+ freeface(faces[j]);
+ memmove(faces+j, faces+j+1, (nfaces-(j+1))*sizeof(Face*));
+ nfaces--;
+ setlast();
+ drawarrows();
+}
+
+void
+dodelete(int i)
+{
+ Face *f;
+
+ f = faces[i];
+ if(history){
+ free(f->str[Sshow]);
+ f->str[Sshow] = estrdup("");
+ }else{
+ delface(i);
+ flushimage(display, 1);
+ }
+}
+
+void
+delete(char *s, char *digest)
+{
+ int i;
+ Face *f;
+
+ lockdisplay(display);
+ for(i=0; i<nfaces; i++){
+ f = faces[i];
+ if(digest != nil){
+ if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest]) == 0){
+ dodelete(i);
+ break;
+ }
+ }else{
+ if(f->str[Sshow] && strcmp(s, f->str[Sshow]) == 0){
+ dodelete(i);
+ break;
+ }
+ }
+ }
+ unlockdisplay(display);
+}
+
+void
+faceproc(void)
+{
+ for(;;)
+ addface(nextface());
+}
+
+void
+resized(void)
+{
+ int i;
+
+ nacross = (Dx(screen->r)-2*facep.x+Facesep)/(Facesize+Facesep);
+ for(ndown=1; rectinrect(facerect(ndown*nacross), screen->r); ndown++)
+ ;
+ setlast();
+ draw(screen, screen->r, bgrnd, nil, ZP);
+ enddate = ZP;
+ drawtime();
+ for(i=0; i<nfaces; i++)
+ drawface(faces[i], i);
+ drawarrows();
+ flushimage(display, 1);
+}
+
+void
+eresized(int new)
+{
+ lockdisplay(display);
+ if(new && getwindow(display, Refnone) < 0) {
+ fprint(2, "can't reattach to window\n");
+ killall("reattach");
+ }
+ resized();
+ unlockdisplay(display);
+}
+
+#if 0
+int
+getmouse(Mouse *m)
+{
+ int n;
+ static int eof;
+ char buf[128];
+
+ if(eof)
+ return 0;
+ for(;;){
+ n = read(mousefd, buf, sizeof(buf));
+ if(n <= 0){
+ /* so callers needn't check return value every time */
+ eof = 1;
+ m->buttons = 0;
+ return 0;
+ }
+ //jpc n = eatomouse(m, buf, n);
+ if(n > 0)
+ return 1;
+ }
+}
+#endif
+int
+getmouse(Mouse *m)
+{
+ static int eof;
+
+ if(eof)
+ return 0;
+ if( readmouse(mousectl) < 0 ) {
+ eof = 1;
+ m->buttons = 0;
+ return 0;
+ }
+ else {
+ *m = mousectl->m;
+/* m->buttons = mousectl->m.buttons;
+ m->xy.x = mousectl->m.xy.x;
+ m->xy.y = mousectl->m.xy.y;
+ m->msec = mousectl->m.msec; */
+ return 1;
+ }
+}
+
+enum
+{
+ Clicksize = 3, /* pixels */
+};
+
+int
+scroll(int but, Point p)
+{
+ int delta;
+
+ delta = 0;
+ lockdisplay(display);
+ if(ptinrect(p, leftr) && first>0){
+ if(but == 2)
+ delta = -first;
+ else{
+ delta = nacross;
+ if(delta > first)
+ delta = first;
+ delta = -delta;
+ }
+ }else if(ptinrect(p, rightr) && last<nfaces){
+ if(but == 2)
+ delta = (nfaces-nacross*ndown) - first;
+ else{
+ delta = nacross;
+ if(delta > nfaces-last)
+ delta = nfaces-last;
+ }
+ }
+ first += delta;
+ last += delta;
+ unlockdisplay(display);
+ if(delta)
+ eresized(0);
+ return delta;
+}
+
+void
+click(int button, Mouse *m)
+{
+ Point p;
+ int i;
+
+ p = m->xy;
+ while(m->buttons == (1<<(button-1)))
+ getmouse(m);
+ if(m->buttons)
+ return;
+ if(abs(p.x-m->xy.x)>Clicksize || abs(p.y-m->xy.y)>Clicksize)
+ return;
+ switch(button){
+ case 1:
+ if(scroll(1, p))
+ break;
+ if(history){
+ /* click clears display */
+ lockdisplay(display);
+ for(i=0; i<nfaces; i++)
+ freeface(faces[i]);
+ free(faces);
+ faces=nil;
+ nfaces = 0;
+ unlockdisplay(display);
+ eresized(0);
+ return;
+ }else{
+ for(i=first; i<last; i++) /* clear vwhois faces */
+ if(ptinrect(p, facerect(i-first))
+ && strstr(faces[i]->str[Sshow], "/XXXvwhois")){
+ delface(i);
+ flushimage(display, 1);
+ }
+ }
+ break;
+ case 2:
+ scroll(2, p);
+ break;
+ case 3:
+ scroll(3, p);
+ lockdisplay(display);
+ for(i=first; i<last; i++)
+ if(ptinrect(p, facerect(i-first))){
+ showmail(faces[i]);
+ break;
+ }
+ unlockdisplay(display);
+ break;
+ }
+}
+
+void
+mouseproc(void *dummy)
+{
+ Mouse mouse;
+
+ while(getmouse(&mouse)){
+ if(mouse.buttons == 1)
+ click(1, &mouse);
+ else if(mouse.buttons == 2)
+ click(2, &mouse);
+ else if(mouse.buttons == 4)
+ click(3, &mouse);
+
+ while(mouse.buttons)
+ getmouse(&mouse);
+ }
+}
+
+void
+killall(char *s)
+{
+ int i, pid;
+
+ pid = getpid();
+ for(i=0; i<NPROC; i++)
+ if(pids[i] && pids[i]!=pid)
+ postnote(PNPROC, pids[i], "kill");
+ threadexitsall(s);
+}
+
+void
+startproc(void (*f)(void), int index)
+{
+ int pid;
+
+ switch(pid = rfork(RFPROC|RFNOWAIT)){ //jpc removed |RFMEM
+ case -1:
+ fprint(2, "faces: fork failed: %r\n");
+ killall("fork failed");
+ case 0:
+ f();
+ fprint(2, "faces: %s process exits\n", procnames[index]);
+ if(index >= 0)
+ killall("process died");
+ threadexitsall(nil);
+ }
+ if(index >= 0)
+ pids[index] = pid;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: faces [-hi] [-m maildir] -W winsize\n");
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i;
+
+ ARGBEGIN{
+ case 'h':
+ history++;
+ break;
+ case 'i':
+ initload++;
+ break;
+ case 'm':
+ addmaildir(EARGF(usage()));
+ maildir = nil;
+ break;
+ case 'W':
+ winsize = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(initdraw(nil, nil, "faces") < 0){
+ fprint(2, "faces: initdraw failed: %r\n");
+ threadexitsall("initdraw");
+ }
+ if(maildir)
+ addmaildir(maildir);
+ init();
+ unlockdisplay(display); /* initdraw leaves it locked */
+ display->locking = 1; /* tell library we're using the display lock */
+ setdate();
+ eresized(0);
+
+ pids[Mainp] = getpid();
+ pids[Timep] = proccreate(timeproc, nil, 16000);
+ pids[Mousep] = proccreate(mouseproc, nil, 16000);
+ if(initload)
+ for(i = 0; i < nmaildirs; i++)
+ loadmboxfaces(maildirs[i]);
+ faceproc();
+ fprint(2, "faces: %s process exits\n", procnames[Mainp]);
+ killall(nil);
+}
diff --git a/src/cmd/faces/mkfile b/src/cmd/faces/mkfile
new file mode 100644
index 00000000..58cc4d25
--- /dev/null
+++ b/src/cmd/faces/mkfile
@@ -0,0 +1,26 @@
+<$PLAN9/src/mkhdr
+
+# default domain for faces, overridden by $facedom
+DEFAULT=\"astro\"
+
+TARG=faces
+
+OFILES=main.$O\
+ facedb.$O\
+ plumb.$O\
+ util.$O\
+
+HFILES=faces.h\
+
+BIN=$PLAN9/bin
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+<$PLAN9/src/mkone
+CFLAGS=$CFLAGS '-DDEFAULT='$DEFAULT
+
+$O.dblook: dblook.$O facedb.$O util.$O
+ $LD -o $target $prereq
diff --git a/src/cmd/faces/plumb.c b/src/cmd/faces/plumb.c
new file mode 100644
index 00000000..a61e2d81
--- /dev/null
+++ b/src/cmd/faces/plumb.c
@@ -0,0 +1,398 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <plumb.h>
+#include <regexp.h>
+#include <bio.h>
+#include <9pclient.h>
+#include "faces.h"
+
+static int showfd = -1;
+static int seefd = -1;
+static int logfd = -1;
+static char *user;
+static char *logtag;
+
+char **maildirs;
+int nmaildirs;
+
+void
+initplumb(void)
+{
+ showfd = plumbopen("send", OWRITE);
+ seefd = plumbopen("seemail", OREAD);
+
+ if(seefd < 0){
+ logfd = open(unsharp("#9/log/mail"), OREAD);
+ seek(logfd, 0LL, 2);
+ user = getenv("user");
+ if(user == nil){
+ fprint(2, "faces: can't find user name: %r\n");
+ exits("$user");
+ }
+ logtag = emalloc(32+strlen(user)+1);
+ sprint(logtag, " delivered %s From ", user);
+ }
+}
+
+void
+addmaildir(char *dir)
+{
+ maildirs = erealloc(maildirs, (nmaildirs+1)*sizeof(char*));
+ maildirs[nmaildirs++] = dir;
+}
+
+char*
+attr(Face *f)
+{
+ static char buf[128];
+
+ if(f->str[Sdigest]){
+ snprint(buf, sizeof buf, "digest=%s", f->str[Sdigest]);
+ return buf;
+ }
+ return nil;
+}
+
+void
+showmail(Face *f)
+{
+ Plumbmsg pm;
+ Plumbattr a;
+ char *s;
+
+ if(showfd<0 || f->str[Sshow]==nil || f->str[Sshow][0]=='\0')
+ return;
+ s = emalloc(strlen("/mail/fs")+1+strlen(f->str[Sshow]));
+ sprint(s,"/mail/fs/%s",f->str[Sshow]);
+ pm.src = "faces";
+ pm.dst = "showmail";
+ pm.wdir = "/mail/fs";
+ pm.type = "text";
+ a.name = "digest";
+ a.value = f->str[Sdigest];
+ a.next = nil;
+ pm.attr = &a;
+ pm.ndata = strlen(s);
+ pm.data = s;
+ plumbsend(showfd,&pm);
+}
+
+char*
+value(Plumbattr *attr, char *key, char *def)
+{
+ char *v;
+
+ v = plumblookup(attr, key);
+ if(v)
+ return v;
+ return def;
+}
+
+void
+setname(Face *f, char *sender)
+{
+ char *at, *bang;
+ char *p;
+
+ /* works with UTF-8, although it's written as ASCII */
+ for(p=sender; *p!='\0'; p++)
+ *p = tolower(*p);
+ f->str[Suser] = sender;
+ at = strchr(sender, '@');
+ if(at){
+ *at++ = '\0';
+ f->str[Sdomain] = estrdup(at);
+ return;
+ }
+ bang = strchr(sender, '!');
+ if(bang){
+ *bang++ = '\0';
+ f->str[Suser] = estrdup(bang);
+ f->str[Sdomain] = sender;
+ return;
+ }
+}
+
+int
+getc(void)
+{
+ static uchar buf[512];
+ static int nbuf = 0;
+ static int i = 0;
+
+ while(i == nbuf){
+ i = 0;
+ nbuf = read(logfd, buf, sizeof buf);
+ if(nbuf == 0){
+ sleep(15000);
+ continue;
+ }
+ if(nbuf < 0)
+ return -1;
+ }
+ return buf[i++];
+}
+
+char*
+getline(char *buf, int n)
+{
+ int i, c;
+
+ for(i=0; i<n-1; i++){
+ c = getc();
+ if(c <= 0)
+ return nil;
+ if(c == '\n')
+ break;
+ buf[i] = c;
+ }
+ buf[i] = '\0';
+ return buf;
+}
+
+static char* months[] = {
+ "jan", "feb", "mar", "apr",
+ "may", "jun", "jul", "aug",
+ "sep", "oct", "nov", "dec"
+};
+
+static int
+getmon(char *s)
+{
+ int i;
+
+ for(i=0; i<nelem(months); i++)
+ if(cistrcmp(months[i], s) == 0)
+ return i;
+ return -1;
+}
+
+/* Fri Jul 23 14:05:14 EDT 1999 */
+ulong
+parsedatev(char **a)
+{
+ char *p;
+ Tm tm;
+
+ memset(&tm, 0, sizeof tm);
+ if((tm.mon=getmon(a[1])) == -1)
+ goto Err;
+ tm.mday = strtol(a[2], &p, 10);
+ if(*p != '\0')
+ goto Err;
+ tm.hour = strtol(a[3], &p, 10);
+ if(*p != ':')
+ goto Err;
+ tm.min = strtol(p+1, &p, 10);
+ if(*p != ':')
+ goto Err;
+ tm.sec = strtol(p+1, &p, 10);
+ if(*p != '\0')
+ goto Err;
+ if(strlen(a[4]) != 3)
+ goto Err;
+ strcpy(tm.zone, a[4]);
+ if(strlen(a[5]) != 4)
+ goto Err;
+ tm.year = strtol(a[5], &p, 10);
+ if(*p != '\0')
+ goto Err;
+ tm.year -= 1900;
+ return tm2sec(&tm);
+Err:
+ return time(0);
+}
+
+ulong
+parsedate(char *s)
+{
+ char *f[10];
+ int nf;
+
+ nf = getfields(s, f, nelem(f), 1, " ");
+ if(nf < 6)
+ return time(0);
+ return parsedatev(f);
+}
+
+/* achille Jul 23 14:05:15 delivered jmk From ms.com!bub Fri Jul 23 14:05:14 EDT 1999 (plan9.bell-labs.com!jmk) 1352 */
+/* achille Oct 26 13:45:42 remote local!rsc From rsc Sat Oct 26 13:45:41 EDT 2002 (rsc) 170 */
+int
+parselog(char *s, char **sender, ulong *xtime)
+{
+ char *f[20];
+ int nf;
+
+ nf = getfields(s, f, nelem(f), 1, " ");
+ if(nf < 14)
+ return 0;
+ if(strcmp(f[4], "delivered") == 0 && strcmp(f[5], user) == 0)
+ goto Found;
+ if(strcmp(f[4], "remote") == 0 && strncmp(f[5], "local!", 6) == 0 && strcmp(f[5]+6, user) == 0)
+ goto Found;
+ return 0;
+
+Found:
+ *sender = estrdup(f[7]);
+ *xtime = parsedatev(&f[8]);
+ return 1;
+}
+
+int
+logrecv(char **sender, ulong *xtime)
+{
+ char buf[4096];
+
+ for(;;){
+ if(getline(buf, sizeof buf) == nil)
+ return 0;
+ if(parselog(buf, sender, xtime))
+ return 1;
+ }
+ return -1;
+}
+
+char*
+tweakdate(char *d)
+{
+ char e[8];
+
+ /* d, date = "Mon Aug 2 23:46:55 EDT 1999" */
+
+ if(strlen(d) < strlen("Mon Aug 2 23:46:55 EDT 1999"))
+ return estrdup("");
+ if(strncmp(date, d, 4+4+3) == 0)
+ snprint(e, sizeof e, "%.5s", d+4+4+3); /* 23:46 */
+ else
+ snprint(e, sizeof e, "%.6s", d+4); /* Aug 2 */
+ return estrdup(e);
+}
+
+Face*
+nextface(void)
+{
+ int i;
+ Face *f;
+ Plumbmsg *m;
+ char *t, *senderp, *showmailp, *digestp;
+ ulong xtime;
+
+ f = emalloc(sizeof(Face));
+ for(;;){
+ if(seefd >= 0){
+ m = plumbrecv(seefd);
+ if(m == nil)
+ killall("error on seemail plumb port");
+ t = value(m->attr, "mailtype", "");
+ if(strcmp(t, "delete") == 0)
+ delete(m->data, value(m->attr, "digest", nil));
+ else if(strcmp(t, "new") != 0)
+ fprint(2, "faces: unknown plumb message type %s\n", t);
+ else for(i=0; i<nmaildirs; i++) {
+ if(strncmp(m->data,"/mail/fs/",strlen("/mail/fs/")) == 0)
+ m->data += strlen("/mail/fs/");
+ if(strncmp(m->data, maildirs[i], strlen(maildirs[i])) == 0)
+ goto Found;
+ }
+ plumbfree(m);
+ continue;
+
+ Found:
+ xtime = parsedate(value(m->attr, "date", date));
+ digestp = value(m->attr, "digest", nil);
+ if(alreadyseen(digestp)){
+ /* duplicate upas/fs can send duplicate messages */
+ plumbfree(m);
+ continue;
+ }
+ senderp = estrdup(value(m->attr, "sender", "???"));
+ showmailp = estrdup(m->data);
+ if(digestp)
+ digestp = estrdup(digestp);
+ plumbfree(m);
+ }else{
+ if(logrecv(&senderp, &xtime) <= 0)
+ killall("error reading log file");
+ showmailp = estrdup("");
+ digestp = nil;
+ }
+ setname(f, senderp);
+ f->time = xtime;
+ f->tm = *localtime(xtime);
+ f->str[Sshow] = showmailp;
+ f->str[Sdigest] = digestp;
+ return f;
+ }
+ return nil;
+}
+
+char*
+iline(char *data, char **pp)
+{
+ char *p;
+
+ for(p=data; *p!='\0' && *p!='\n'; p++)
+ ;
+ if(*p == '\n')
+ *p++ = '\0';
+ *pp = p;
+ return data;
+}
+
+Face*
+dirface(char *dir, char *num)
+{
+ Face *f;
+ char *from, *date;
+ char buf[1024], *info, *p, *digest;
+ int n;
+ ulong len;
+ CFid *fid;
+
+#if 0
+ /*
+ * loadmbox leaves us in maildir, so we needn't
+ * walk /mail/fs/mbox for each face; this makes startup
+ * a fair bit quicker.
+ */
+ if(getwd(pwd, sizeof pwd) != nil && strcmp(pwd, dir) == 0)
+ sprint(buf, "%s/info", num);
+ else
+ sprint(buf, "%s/%s/info", dir, num);
+#endif
+ sprint(buf, "%s/%s/info", dir, num);
+ len = fsdirlen(upasfs, buf);
+ if(len <= 0)
+ return nil;
+ fid = fsopen(upasfs,buf, OREAD);
+ if(fid == nil)
+ return nil;
+ info = emalloc(len+1);
+ n = fsreadn(fid, info, len);
+ fsclose(fid);
+ if(n < 0){
+ free(info);
+ return nil;
+ }
+ info[n] = '\0';
+ f = emalloc(sizeof(Face));
+ from = iline(info, &p); /* from */
+ iline(p, &p); /* to */
+ iline(p, &p); /* cc */
+ iline(p, &p); /* replyto */
+ date = iline(p, &p); /* date */
+ setname(f, estrdup(from));
+ f->time = parsedate(date);
+ f->tm = *localtime(f->time);
+ sprint(buf, "%s/%s", dir, num);
+ f->str[Sshow] = estrdup(buf);
+ iline(p, &p); /* subject */
+ iline(p, &p); /* mime content type */
+ iline(p, &p); /* mime disposition */
+ iline(p, &p); /* filename */
+ digest = iline(p, &p); /* digest */
+ f->str[Sdigest] = estrdup(digest);
+ free(info);
+ return f;
+}
diff --git a/src/cmd/faces/util.c b/src/cmd/faces/util.c
new file mode 100644
index 00000000..22f57549
--- /dev/null
+++ b/src/cmd/faces/util.c
@@ -0,0 +1,42 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <plumb.h>
+#include <9pclient.h>
+#include "faces.h"
+
+void*
+emalloc(ulong sz)
+{
+ void *v;
+ v = malloc(sz);
+ if(v == nil) {
+ fprint(2, "out of memory allocating %ld\n", sz);
+ exits("mem");
+ }
+ memset(v, 0, sz);
+ return v;
+}
+
+void*
+erealloc(void *v, ulong sz)
+{
+ v = realloc(v, sz);
+ if(v == nil) {
+ fprint(2, "out of memory allocating %ld\n", sz);
+ exits("mem");
+ }
+ return v;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+ if((t = strdup(s)) == nil) {
+ fprint(2, "out of memory in strdup(%.10s)\n", s);
+ exits("mem");
+ }
+ return t;
+}
+