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