]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/faces/facedb.c
Import sources from 2011-03-30 iso image - lib
[plan9front.git] / sys / src / cmd / faces / facedb.c
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
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
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*
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*
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*
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*
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*
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*
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*
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*
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
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
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*
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*
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*
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
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 }