dots

Personal dotfiles
git clone git://git.gormless.xyz/dots.git
Log | Files | Refs

facedb.c (10715B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <draw.h>
      4 #include <plumb.h>
      5 #include <regexp.h>
      6 #include <bio.h>
      7 #include <9pclient.h>
      8 #include "faces.h"
      9 
     10 enum	/* number of deleted faces to cache */
     11 {
     12 	Nsave	= 20
     13 };
     14 
     15 static Facefile	*facefiles;
     16 static int		nsaved;
     17 static char	*facedom;
     18 static char	*libface;
     19 static char	*homeface;
     20 
     21 /*
     22  * Loading the files is slow enough on a dial-up line to be worth this trouble
     23  */
     24 typedef struct Readcache	Readcache;
     25 struct Readcache {
     26 	char *file;
     27 	char *data;
     28 	long mtime;
     29 	long rdtime;
     30 	Readcache *next;
     31 };
     32 
     33 static Readcache *rcache;
     34 
     35 ulong
     36 dirlen(char *s)
     37 {
     38 	Dir *d;
     39 	ulong len;
     40 
     41 	d = dirstat(s);
     42 	if(d == nil)
     43 		return 0;
     44 	len = d->length;
     45 	free(d);
     46 	return len;
     47 }
     48 
     49 ulong
     50 fsdirlen(CFsys *fs,char *s)
     51 {
     52 	Dir *d;
     53 	ulong len;
     54 
     55 	d = fsdirstat(fs,s);
     56 	if(d == nil)
     57 		return 0;
     58 	len = d->length;
     59 	free(d);
     60 	return len;
     61 }
     62 
     63 ulong
     64 dirmtime(char *s)
     65 {
     66 	Dir *d;
     67 	ulong t;
     68 
     69 	d = dirstat(s);
     70 	if(d == nil)
     71 		return 0;
     72 	t = d->mtime;
     73 	free(d);
     74 	return t;
     75 }
     76 
     77 static char*
     78 doreadfile(char *s)
     79 {
     80 	char *p;
     81 	int fd, n;
     82 	ulong len;
     83 
     84 	len = dirlen(s);
     85 	if(len == 0)
     86 		return nil;
     87 
     88 	p = malloc(len+1);
     89 	if(p == nil)
     90 		return nil;
     91 
     92 	if((fd = open(s, OREAD)) < 0
     93 	|| (n = readn(fd, p, len)) < 0) {
     94 		close(fd);
     95 		free(p);
     96 		return nil;
     97 	}
     98 
     99 	p[n] = '\0';
    100 	return p;
    101 }
    102 
    103 static char*
    104 readfile(char *s)
    105 {
    106 	Readcache *r, **l;
    107 	char *p;
    108 	ulong mtime;
    109 
    110 	for(l=&rcache, r=*l; r; l=&r->next, r=*l) {
    111 		if(strcmp(r->file, s) != 0)
    112 			continue;
    113 
    114 		/*
    115 		 * if it's less than 30 seconds since we read it, or it
    116 		 * hasn't changed, send back our copy
    117 		 */
    118 		if(time(0) - r->rdtime < 30)
    119 			return strdup(r->data);
    120 		if(dirmtime(s) == r->mtime) {
    121 			r->rdtime = time(0);
    122 			return strdup(r->data);
    123 		}
    124 
    125 		/* out of date, remove this and fall out of loop */
    126 		*l = r->next;
    127 		free(r->file);
    128 		free(r->data);
    129 		free(r);
    130 		break;
    131 	}
    132 
    133 	/* add to cache */
    134 	mtime = dirmtime(s);
    135 	if(mtime == 0)
    136 		return nil;
    137 
    138 	if((p = doreadfile(s)) == nil)
    139 		return nil;
    140 
    141 	r = malloc(sizeof(*r));
    142 	if(r == nil)
    143 		return nil;
    144 	r->mtime = mtime;
    145 	r->file = estrdup(s);
    146 	r->data = p;
    147 	r->rdtime = time(0);
    148 	r->next = rcache;
    149 	rcache = r;
    150 	return strdup(r->data);
    151 }
    152 
    153 static char*
    154 translatedomain(char *dom, char *list)
    155 {
    156 	static char buf[200];
    157 	char *p, *ep, *q, *nextp, *file;
    158 	char *bbuf, *ebuf;
    159 	Reprog *exp;
    160 
    161 	if(dom == nil || *dom == 0)
    162 		return nil;
    163 
    164 	if(list == nil || (file = readfile(list)) == nil)
    165 		return dom;
    166 
    167 	for(p=file; p; p=nextp) {
    168 		if(nextp = strchr(p, '\n'))
    169 			*nextp++ = '\0';
    170 
    171 		if(*p == '#' || (q = strpbrk(p, " \t")) == nil || q-p > sizeof(buf)-2)
    172 			continue;
    173 
    174 		bbuf = buf+1;
    175 		ebuf = buf+(1+(q-p));
    176 		strncpy(bbuf, p, ebuf-bbuf);
    177 		*ebuf = 0;
    178 		if(*bbuf != '^')
    179 			*--bbuf = '^';
    180 		if(ebuf[-1] != '$') {
    181 			*ebuf++ = '$';
    182 			*ebuf = 0;
    183 		}
    184 
    185 		if((exp = regcomp(bbuf)) == nil){
    186 			fprint(2, "bad regexp in machinelist: %s\n", bbuf);
    187 			killall("regexp");
    188 		}
    189 
    190 		if(regexec(exp, dom, 0, 0)){
    191 			free(exp);
    192 			ep = p+strlen(p);
    193 			q += strspn(q, " \t");
    194 			if(ep-q+2 > sizeof buf) {
    195 				fprint(2, "huge replacement in machinelist: %.*s\n", utfnlen(q, ep-q), q);
    196 				exits("bad big replacement");
    197 			}
    198 			strncpy(buf, q, ep-q);
    199 			ebuf = buf+(ep-q);
    200 			*ebuf = 0;
    201 			while(ebuf > buf && (ebuf[-1] == ' ' || ebuf[-1] == '\t'))
    202 				*--ebuf = 0;
    203 			free(file);
    204 			return buf;
    205 		}
    206 		free(exp);
    207 	}
    208 	free(file);
    209 
    210 	return dom;
    211 }
    212 
    213 static char*
    214 tryfindpicture(char *dom, char *user, char *dir, char *dict)
    215 {
    216 	static char buf[1024];
    217 	char *file, *p, *nextp, *q;
    218 
    219 	if((file = readfile(dict)) == nil)
    220 		return nil;
    221 
    222 	snprint(buf, sizeof buf, "%s/%s", dom, user);
    223 
    224 	for(p=file; p; p=nextp){
    225 		if(nextp = strchr(p, '\n'))
    226 			*nextp++ = '\0';
    227 
    228 		if(*p == '#' || (q = strpbrk(p, " \t")) == nil)
    229 			continue;
    230 		*q++ = 0;
    231 
    232 		if(strcmp(buf, p) == 0){
    233 			q += strspn(q, " \t");
    234 			snprint(buf, sizeof buf, "%s/%s", dir, q);
    235 			q = buf+strlen(buf);
    236 			while(q > buf && (q[-1] == ' ' || q[-1] == '\t'))
    237 				*--q = 0;
    238 			free(file);
    239 			return estrdup(buf);
    240 		}
    241 	}
    242 	free(file);
    243 	return nil;
    244 }
    245 
    246 static char*
    247 estrstrdup(char *a, char *b)
    248 {
    249 	char *t;
    250 
    251 	t = emalloc(strlen(a)+strlen(b)+1);
    252 	strcpy(t, a);
    253 	strcat(t, b);
    254 	return t;
    255 }
    256 
    257 static char*
    258 tryfindfiledir(char *dom, char *user, char *dir)
    259 {
    260 	char *dict, *ndir, *x, *odom;
    261 	int fd;
    262 	int i, n;
    263 	Dir *d;
    264 
    265 	/*
    266 	 * If this directory has a .machinelist, use it.
    267 	 */
    268 	x = estrstrdup(dir, "/.machinelist");
    269 	dom = estrdup(translatedomain(dom, x));
    270 	free(x);
    271 	/*
    272 	 * If this directory has a .dict, use it.
    273 	 */
    274 	dict = estrstrdup(dir, "/.dict");
    275 	if(access(dict, AEXIST) >= 0){
    276 		x = tryfindpicture(dom, user, dir, dict);
    277 		free(dict);
    278 		free(dom);
    279 		return x;
    280 	}
    281 	free(dict);
    282 
    283 	/*
    284 	 * If not, recurse into subdirectories.
    285 	 * Ignore 48x48xN directories for now.
    286 	 */
    287 	if((fd = open(dir, OREAD)) < 0)
    288 		return nil;
    289 	while((n = dirread(fd, &d)) > 0){
    290 		for(i=0; i<n; i++){
    291 			if((d[i].mode&DMDIR)&& strncmp(d[i].name, "48x48x", 6) != 0){
    292 				ndir = emalloc(strlen(dir)+1+strlen(d[i].name)+1);
    293 				strcpy(ndir, dir);
    294 				strcat(ndir, "/");
    295 				strcat(ndir, d[i].name);
    296 				if((x = tryfindfiledir(dom, user, ndir)) != nil){
    297 					free(ndir);
    298 					free(d);
    299 					close(fd);
    300 					free(dom);
    301 					return x;
    302 				}
    303 			}
    304 		}
    305 		free(d);
    306 	}
    307 	close(fd);
    308 
    309 	/*
    310 	 * Handle 48x48xN directories in the right order.
    311 	 */
    312 	ndir = estrstrdup(dir, "/48x48x8");
    313 	for(i=8; i>0; i>>=1){
    314 		ndir[strlen(ndir)-1] = i+'0';
    315 		if(access(ndir, AEXIST) >= 0 && (x = tryfindfiledir(dom, user, ndir)) != nil){
    316 			free(ndir);
    317 			free(dom);
    318 			return x;
    319 		}
    320 	}
    321 	free(ndir);
    322 	free(dom);
    323 	return nil;
    324 }
    325 
    326 static char*
    327 tryfindfile(char *dom, char *user)
    328 {
    329 	char *p;
    330 
    331 	while(dom && *dom){
    332 		if(homeface && (p = tryfindfiledir(dom, user, homeface)) != nil)
    333 			return p;
    334 		if((p = tryfindfiledir(dom, user, libface)) != nil)
    335 			return p;
    336 		if((dom = strchr(dom, '.')) == nil)
    337 			break;
    338 		dom++;
    339 	}
    340 	return nil;
    341 }
    342 
    343 char*
    344 findfile(Face *f, char *dom, char *user)
    345 {
    346 	char *p;
    347 
    348 	if(facedom == nil){
    349 		facedom = getenv("facedom");
    350 		if(facedom == nil)
    351 			facedom = DEFAULT;
    352 	}
    353 	if(libface == nil)
    354 		libface = unsharp("#9/face");
    355 	if(homeface == nil)
    356 		homeface = smprint("%s/lib/face", getenv("HOME"));
    357 
    358 	if(dom == nil)
    359 		dom = facedom;
    360 
    361 	f->unknown = 0;
    362 	if((p = tryfindfile(dom, user)) != nil)
    363 		return p;
    364 	f->unknown = 1;
    365 	p = tryfindfile(dom, "unknown");
    366 	if(p != nil || strcmp(dom, facedom) == 0)
    367 		return p;
    368 	return tryfindfile("unknown", "unknown");
    369 }
    370 
    371 static
    372 void
    373 clearsaved(void)
    374 {
    375 	Facefile *f, *next, **lf;
    376 
    377 	lf = &facefiles;
    378 	for(f=facefiles; f!=nil; f=next){
    379 		next = f->next;
    380 		if(f->ref > 0){
    381 			*lf = f;
    382 			lf = &(f->next);
    383 			continue;
    384 		}
    385 		if(f->image != display->black && f->image != display->white)
    386 			freeimage(f->image);
    387 		free(f->file);
    388 		free(f);
    389 	}
    390 	*lf = nil;
    391 	nsaved = 0;
    392 }
    393 
    394 void
    395 freefacefile(Facefile *f)
    396 {
    397 	if(f==nil || f->ref-->1)
    398 		return;
    399 	if(++nsaved > Nsave)
    400 		clearsaved();
    401 }
    402 
    403 static Image*
    404 myallocimage(ulong chan)
    405 {
    406 	Image *img;
    407 	img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
    408 	if(img == nil){
    409 		clearsaved();
    410 		img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
    411 		if(img == nil)
    412 			return nil;
    413 	}
    414 	return img;
    415 }
    416 
    417 
    418 static Image*
    419 readbit(int fd, ulong chan)
    420 {
    421 	char buf[4096], hx[4], *p;
    422 	uchar data[Facesize*Facesize];	/* more than enough */
    423 	int nhx, i, n, ndata, nbit;
    424 	Image *img;
    425 
    426 	n = readn(fd, buf, sizeof buf);
    427 	if(n <= 0)
    428 		return nil;
    429 	if(n >= sizeof buf)
    430 		n = sizeof(buf)-1;
    431 	buf[n] = '\0';
    432 
    433 	n = 0;
    434 	nhx = 0;
    435 	nbit = chantodepth(chan);
    436 	ndata = (Facesize*Facesize*nbit)/8;
    437 	p = buf;
    438 	while(n < ndata) {
    439 		p = strpbrk(p+1, "0123456789abcdefABCDEF");
    440 		if(p == nil)
    441 			break;
    442 		if(p[0] == '0' && p[1] == 'x')
    443 			continue;
    444 
    445 		hx[nhx] = *p;
    446 		if(++nhx == 2) {
    447 			hx[nhx] = 0;
    448 			i = strtoul(hx, 0, 16);
    449 			data[n++] = i;
    450 			nhx = 0;
    451 		}
    452 	}
    453 	if(n < ndata)
    454 		return allocimage(display, Rect(0,0,Facesize,Facesize), CMAP8, 0, 0x88888888);
    455 
    456 	img = myallocimage(chan);
    457 	if(img == nil)
    458 		return nil;
    459 	loadimage(img, img->r, data, ndata);
    460 	return img;
    461 }
    462 
    463 static Facefile*
    464 readface(char *fn)
    465 {
    466 	int x, y, fd;
    467 	uchar bits;
    468 	uchar *p;
    469 	Image *mask;
    470 	Image *face;
    471 	char buf[16];
    472 	uchar data[Facesize*Facesize];
    473 	uchar mdata[(Facesize*Facesize)/8];
    474 	Facefile *f;
    475 	Dir *d;
    476 
    477 	for(f=facefiles; f!=nil; f=f->next){
    478 		if(strcmp(fn, f->file) == 0){
    479 			if(f->image == nil)
    480 				break;
    481 			if(time(0) - f->rdtime >= 30) {
    482 				if(dirmtime(fn) != f->mtime){
    483 					f = nil;
    484 					break;
    485 				}
    486 				f->rdtime = time(0);
    487 			}
    488 			f->ref++;
    489 			return f;
    490 		}
    491 	}
    492 
    493 	if((fd = open(fn, OREAD)) < 0)
    494 		return nil;
    495 
    496 	if(readn(fd, buf, sizeof buf) != sizeof buf){
    497 		close(fd);
    498 		return nil;
    499 	}
    500 
    501 	seek(fd, 0, 0);
    502 
    503 	mask = nil;
    504 	if(buf[0] == '0' && buf[1] == 'x'){
    505 		/* greyscale faces are just masks that we draw black through! */
    506 		if(buf[2+8] == ',')	/* ldepth 1 */
    507 			mask = readbit(fd, GREY2);
    508 		else
    509 			mask = readbit(fd, GREY1);
    510 		face = display->black;
    511 	}else{
    512 		face = readimage(display, fd, 0);
    513 		if(face == nil)
    514 			goto Done;
    515 		else if(face->chan == GREY4 || face->chan == GREY8){	/* greyscale: use inversion as mask */
    516 			mask = myallocimage(face->chan);
    517 			/* okay if mask is nil: that will copy the image white background and all */
    518 			if(mask == nil)
    519 				goto Done;
    520 
    521 			/* invert greyscale image */
    522 			draw(mask, mask->r, display->white, nil, ZP);
    523 			gendraw(mask, mask->r, display->black, ZP, face, face->r.min);
    524 			freeimage(face);
    525 			face = display->black;
    526 		}else if(face->depth == 8){	/* snarf the bytes back and do a fill. */
    527 			mask = myallocimage(GREY1);
    528 			if(mask == nil)
    529 				goto Done;
    530 			if(unloadimage(face, face->r, data, Facesize*Facesize) != Facesize*Facesize){
    531 				freeimage(mask);
    532 				goto Done;
    533 			}
    534 			bits = 0;
    535 			p = mdata;
    536 			for(y=0; y<Facesize; y++){
    537 				for(x=0; x<Facesize; x++){
    538 					bits <<= 1;
    539 					if(data[Facesize*y+x] != 0xFF)
    540 						bits |= 1;
    541 					if((x&7) == 7)
    542 						*p++ = bits&0xFF;
    543 				}
    544 			}
    545 			if(loadimage(mask, mask->r, mdata, sizeof mdata) != sizeof mdata){
    546 				freeimage(mask);
    547 				goto Done;
    548 			}
    549 		}
    550 	}
    551 
    552 Done:
    553 	/* always add at beginning of list, so updated files don't collide in cache */
    554 	if(f == nil){
    555 		f = emalloc(sizeof(Facefile));
    556 		f->file = estrdup(fn);
    557 		d = dirfstat(fd);
    558 		if(d != nil){
    559 			f->mtime = d->mtime;
    560 			free(d);
    561 		}
    562 		f->next = facefiles;
    563 		facefiles = f;
    564 	}
    565 	f->ref++;
    566 	f->image = face;
    567 	f->mask = mask;
    568 	f->rdtime = time(0);
    569 	close(fd);
    570 	return f;
    571 }
    572 
    573 void
    574 findbit(Face *f)
    575 {
    576 	char *fn;
    577 
    578 	fn = findfile(f, f->str[Sdomain], f->str[Suser]);
    579 	if(fn) {
    580 		if(strstr(fn, "unknown"))
    581 			f->unknown = 1;
    582 		f->file = readface(fn);
    583 	}
    584 	if(f->file){
    585 		f->bit = f->file->image;
    586 		f->mask = f->file->mask;
    587 	}else{
    588 		/* if returns nil, this is still ok: draw(nil) works */
    589 		f->bit = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow);
    590 		replclipr(f->bit, 1, Rect(0, 0, Facesize, Facesize));
    591 		f->mask = nil;
    592 	}
    593 }