#include #include #include #include #include #include #include #include #include "faces.h" #include "webfs.h" enum /* number of deleted faces to cache */ { Nsave = 20, }; static Facefile *facefiles; static int nsaved; static char *facedom; static char *homeface; /* * 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 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, char *list) { static char buf[200]; char *p, *ep, *q, *nextp, *file; char *bbuf, *ebuf; Reprog *exp; if(dom == nil || *dom == 0) return nil; if(list == nil || (file = readfile(list)) == 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(char *dom, char *user, char *dir, char *dict) { static char buf[1024]; char *file, *p, *nextp, *q; if((file = readfile(dict)) == 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"); snprint(buf, sizeof buf, "%s/%s", dir, q); q = buf+strlen(buf); while(q > buf && (q[-1] == ' ' || q[-1] == '\t')) *--q = 0; free(file); return estrdup(buf); } } free(file); return nil; } static char* estrstrdup(char *a, char *b) { char *t; t = emalloc(strlen(a)+strlen(b)+1); strcpy(t, a); strcat(t, b); return t; } static char* tryfindfiledir(char *dom, char *user, char *dir) { char *dict, *ndir, *x; int fd; int i, n; Dir *d; /* * If this directory has a .machinelist, use it. */ x = estrstrdup(dir, "/.machinelist"); dom = estrdup(translatedomain(dom, x)); free(x); /* * If this directory has a .dict, use it. */ dict = estrstrdup(dir, "/.dict"); if(access(dict, AEXIST) >= 0){ x = tryfindpicture(dom, user, dir, dict); free(dict); free(dom); return x; } free(dict); /* * If not, recurse into subdirectories. * Ignore 512x512 directories. * Save 48x48 directories for later. */ if((fd = open(dir, OREAD)) < 0){ free(dom); return nil; } while((n = dirread(fd, &d)) > 0){ for(i=0; i0; i>>=1){ ndir[strlen(ndir)-1] = i+'0'; if(access(ndir, AEXIST) >= 0 && (x = tryfindfiledir(dom, user, ndir)) != nil){ free(ndir); free(dom); return x; } } free(ndir); free(dom); return nil; } static char* tryfindfile(char *dom, char *user) { char *p; while(dom && *dom){ if(homeface && (p = tryfindfiledir(dom, user, homeface)) != nil) return p; if((p = tryfindfiledir(dom, user, "/lib/face")) != nil) return p; if((dom = strchr(dom, '.')) == nil) break; dom++; } return nil; } char* findfile(Face *f, char *dom, char *user) { char *p; if(facedom == nil){ facedom = getenv("facedom"); if(facedom == nil) facedom = DEFAULT; } if(dom == nil) dom = facedom; if(homeface == nil){ if((p = getenv("home")) != nil){ homeface = smprint("%s/lib/face", p); free(p); } } f->unknown = 0; if((p = tryfindfile(dom, user)) != nil) return p; f->unknown = 1; p = tryfindfile(dom, "unknown"); if(p != nil || strcmp(dom, facedom) == 0) return p; return tryfindfile("unknown", "unknown"); } 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; yr, 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; } static int pipeline(char *fmt, ...) { char buf[1024]; va_list a; int p[2]; va_start(a, fmt); vsnprint(buf, sizeof(buf), fmt, a); va_end(a); if(pipe(p) < 0) return -1; switch(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG|RFREND)){ case -1: close(p[0]); close(p[1]); return -1; case 0: close(p[1]); dup(p[0], 0); dup(p[0], 1); close(p[0]); execl("/bin/rc", "rc", "-c", buf, nil); exits("exec"); } close(p[0]); return p[1]; } static Image* gravatar(char *user, char *domain) { char email[256], *pic; uchar digest[MD5dlen]; int i, j, n, fd; Image *mug; Webfs *w; fmtinstall('H', encodefmt); i = snprint(email, sizeof email, "%s@%s", user, domain); md5((uchar*)email, i, digest, nil); w = webfs("https://secure.gravatar.com/avatar/%.*lH?s=48&d=404", sizeof digest, digest); if(w == nil) return nil; pic = mallocz(32*1024, 1); if(pic == nil){ webfsfree(w); return nil; } i = webfsget(w, pic, 32*1024); webfsfree(w); if(i < 0){ free(pic); return nil; } if(strcmp(pic, "\211PNG\r\n\032\n") == 0) fd = pipeline("png -9 >[2]/dev/null"); else fd = pipeline("jpg -9 >[2]/dev/null"); j = 0; while(j < i){ if((n = write(fd, pic+j, i-j)) < 0){ close(fd); free(pic); return nil; } j += n; } mug = readimage(display, fd, 0); close(fd); return mug; } void findbit(Face *f) { char *fn; if((f->bit = gravatar(f->str[Suser], f->str[Sdomain])) != nil){ f->mask = nil; return; } 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; } free(fn); }