]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/faces/facedb.c
awk: make empty FS unicodely-correct.
[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                 free(dom);
275                 return nil;
276         }
277         while((n = dirread(fd, &d)) > 0){
278                 for(i=0; i<n; i++){
279                         if((d[i].mode&DMDIR)
280                         && strncmp(d[i].name, "512x", 4) != 0
281                         && strncmp(d[i].name, "48x48x", 6) != 0){
282                                 ndir = emalloc(strlen(dir)+1+strlen(d[i].name)+1);
283                                 strcpy(ndir, dir);
284                                 strcat(ndir, "/");
285                                 strcat(ndir, d[i].name);
286                                 if((x = tryfindfiledir(dom, user, ndir)) != nil){
287                                         free(ndir);
288                                         free(d);
289                                         close(fd);
290                                         free(dom);
291                                         return x;
292                                 }
293                                 free(ndir);
294                         }
295                 }
296                 free(d);
297         }
298         close(fd);
299         
300         /*
301          * Handle 48x48 directories in the right order.
302          */
303         ndir = estrstrdup(dir, "/48x48x8");
304         for(i=8; i>0; i>>=1){
305                 ndir[strlen(ndir)-1] = i+'0';
306                 if(access(ndir, AEXIST) >= 0 && (x = tryfindfiledir(dom, user, ndir)) != nil){
307                         free(ndir);
308                         free(dom);
309                         return x;
310                 }
311         }
312         free(ndir);
313         free(dom);
314         return nil;
315 }
316
317 static char*
318 tryfindfile(char *dom, char *user)
319 {
320         char *p;
321
322         while(dom && *dom){
323                 if(homeface && (p = tryfindfiledir(dom, user, homeface)) != nil)
324                         return p;
325                 if((p = tryfindfiledir(dom, user, "/lib/face")) != nil)
326                         return p;
327                 if((dom = strchr(dom, '.')) == nil)
328                         break;
329                 dom++;
330         }
331         return nil;
332 }
333
334 char*
335 findfile(Face *f, char *dom, char *user)
336 {
337         char *p;
338
339         if(facedom == nil){
340                 facedom = getenv("facedom");
341                 if(facedom == nil)
342                         facedom = DEFAULT;
343         }
344         if(dom == nil)
345                 dom = facedom;
346         if(homeface == nil){
347                 if((p = getenv("home")) != nil){
348                         homeface = smprint("%s/lib/face", p);
349                         free(p);
350                 }
351         }
352
353         f->unknown = 0;
354         if((p = tryfindfile(dom, user)) != nil)
355                 return p;
356         f->unknown = 1;
357         p = tryfindfile(dom, "unknown");
358         if(p != nil || strcmp(dom, facedom) == 0)
359                 return p;
360         return tryfindfile("unknown", "unknown");
361 }
362
363 static
364 void
365 clearsaved(void)
366 {
367         Facefile *f, *next, **lf;
368
369         lf = &facefiles;
370         for(f=facefiles; f!=nil; f=next){
371                 next = f->next;
372                 if(f->ref > 0){
373                         *lf = f;
374                         lf = &(f->next);
375                         continue;
376                 }
377                 if(f->image != display->black && f->image != display->white)
378                         freeimage(f->image);
379                 free(f->file);
380                 free(f);
381         }
382         *lf = nil;
383         nsaved = 0;
384 }
385
386 void
387 freefacefile(Facefile *f)
388 {
389         if(f==nil || f->ref-->1)
390                 return;
391         if(++nsaved > Nsave)
392                 clearsaved();
393 }       
394
395 static Image*
396 myallocimage(ulong chan)
397 {
398         Image *img;
399         img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
400         if(img == nil){
401                 clearsaved();
402                 img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
403                 if(img == nil)
404                         return nil;
405         }
406         return img;
407 }
408                 
409
410 static Image*
411 readbit(int fd, ulong chan)
412 {
413         char buf[4096], hx[4], *p;
414         uchar data[Facesize*Facesize];  /* more than enough */
415         int nhx, i, n, ndata, nbit;
416         Image *img;
417
418         n = readn(fd, buf, sizeof buf);
419         if(n <= 0)
420                 return nil;
421         if(n >= sizeof buf)
422                 n = sizeof(buf)-1;
423         buf[n] = '\0';
424
425         n = 0;
426         nhx = 0;
427         nbit = chantodepth(chan);
428         ndata = (Facesize*Facesize*nbit)/8;
429         p = buf;
430         while(n < ndata) {
431                 p = strpbrk(p+1, "0123456789abcdefABCDEF");
432                 if(p == nil)
433                         break;
434                 if(p[0] == '0' && p[1] == 'x')
435                         continue;
436
437                 hx[nhx] = *p;
438                 if(++nhx == 2) {
439                         hx[nhx] = 0;
440                         i = strtoul(hx, 0, 16);
441                         data[n++] = i;
442                         nhx = 0;
443                 }
444         }
445         if(n < ndata)
446                 return allocimage(display, Rect(0,0,Facesize,Facesize), CMAP8, 0, 0x88888888);
447
448         img = myallocimage(chan);
449         if(img == nil)
450                 return nil;
451         loadimage(img, img->r, data, ndata);
452         return img;
453 }
454
455 static Facefile*
456 readface(char *fn)
457 {
458         int x, y, fd;
459         uchar bits;
460         uchar *p;
461         Image *mask;
462         Image *face;
463         char buf[16];
464         uchar data[Facesize*Facesize];
465         uchar mdata[(Facesize*Facesize)/8];
466         Facefile *f;
467         Dir *d;
468
469         for(f=facefiles; f!=nil; f=f->next){
470                 if(strcmp(fn, f->file) == 0){
471                         if(f->image == nil)
472                                 break;
473                         if(time(0) - f->rdtime >= 30) {
474                                 if(dirmtime(fn) != f->mtime){
475                                         f = nil;
476                                         break;
477                                 }
478                                 f->rdtime = time(0);
479                         }
480                         f->ref++;
481                         return f;
482                 }
483         }
484
485         if((fd = open(fn, OREAD)) < 0)
486                 return nil;
487
488         if(readn(fd, buf, sizeof buf) != sizeof buf){
489                 close(fd);
490                 return nil;
491         }
492
493         seek(fd, 0, 0);
494
495         mask = nil;
496         if(buf[0] == '0' && buf[1] == 'x'){
497                 /* greyscale faces are just masks that we draw black through! */
498                 if(buf[2+8] == ',')     /* ldepth 1 */
499                         mask = readbit(fd, GREY2);
500                 else
501                         mask = readbit(fd, GREY1);
502                 face = display->black;
503         }else{
504                 face = readimage(display, fd, 0);
505                 if(face == nil)
506                         goto Done;
507                 else if(face->chan == GREY4 || face->chan == GREY8){    /* greyscale: use inversion as mask */
508                         mask = myallocimage(face->chan);
509                         /* okay if mask is nil: that will copy the image white background and all */
510                         if(mask == nil)
511                                 goto Done;
512
513                         /* invert greyscale image */
514                         draw(mask, mask->r, display->white, nil, ZP);
515                         gendraw(mask, mask->r, display->black, ZP, face, face->r.min);
516                         freeimage(face);
517                         face = display->black;
518                 }else if(face->depth == 8){     /* snarf the bytes back and do a fill. */
519                         mask = myallocimage(GREY1);
520                         if(mask == nil)
521                                 goto Done;
522                         if(unloadimage(face, face->r, data, Facesize*Facesize) != Facesize*Facesize){   
523                                 freeimage(mask);
524                                 goto Done;
525                         }
526                         bits = 0;
527                         p = mdata;
528                         for(y=0; y<Facesize; y++){
529                                 for(x=0; x<Facesize; x++){      
530                                         bits <<= 1;
531                                         if(data[Facesize*y+x] != 0xFF)
532                                                 bits |= 1;
533                                         if((x&7) == 7)
534                                                 *p++ = bits&0xFF;
535                                 }
536                         }
537                         if(loadimage(mask, mask->r, mdata, sizeof mdata) != sizeof mdata){
538                                 freeimage(mask);
539                                 goto Done;
540                         }
541                 }
542         }
543
544 Done:
545         /* always add at beginning of list, so updated files don't collide in cache */
546         if(f == nil){
547                 f = emalloc(sizeof(Facefile));
548                 f->file = estrdup(fn);
549                 d = dirfstat(fd);
550                 if(d != nil){
551                         f->mtime = d->mtime;
552                         free(d);
553                 }
554                 f->next = facefiles;
555                 facefiles = f;
556         }
557         f->ref++;
558         f->image = face;
559         f->mask = mask;
560         f->rdtime = time(0);
561         close(fd);
562         return f;
563 }
564
565 void
566 findbit(Face *f)
567 {
568         char *fn;
569
570         fn = findfile(f, f->str[Sdomain], f->str[Suser]);
571         if(fn) {
572                 if(strstr(fn, "unknown"))
573                         f->unknown = 1;
574                 f->file = readface(fn);
575         }
576         if(f->file){
577                 f->bit = f->file->image;
578                 f->mask = f->file->mask;
579         }else{
580                 /* if returns nil, this is still ok: draw(nil) works */
581                 f->bit = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow);
582                 replclipr(f->bit, 1, Rect(0, 0, Facesize, Facesize));
583                 f->mask = nil;
584         }
585         free(fn);
586 }