blob: a24a391ad60b86091ff3b14d57a243f59643fad1 [file] [log] [blame]
#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;
static char *libface;
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
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, 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, *odom;
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 48x48xN directories for now.
*/
if((fd = open(dir, OREAD)) < 0)
return nil;
while((n = dirread(fd, &d)) > 0){
for(i=0; i<n; i++){
if((d[i].mode&DMDIR)&& strncmp(d[i].name, "48x48x", 6) != 0){
ndir = emalloc(strlen(dir)+1+strlen(d[i].name)+1);
strcpy(ndir, dir);
strcat(ndir, "/");
strcat(ndir, d[i].name);
if((x = tryfindfiledir(dom, user, ndir)) != nil){
free(ndir);
free(d);
close(fd);
free(dom);
return x;
}
}
}
free(d);
}
close(fd);
/*
* Handle 48x48xN directories in the right order.
*/
ndir = estrstrdup(dir, "/48x48x8");
for(i=8; i>0; 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, libface)) != 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(libface == nil)
libface = unsharp("#9/face");
if(homeface == nil)
homeface = smprint("%s/lib/face", getenv("HOME"));
if(dom == nil)
dom = facedom;
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; 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;
}
}