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 }