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