Portability enhancements to make better use of autoconf. All system
[sgt/agedu] / html.c
CommitLineData
70322ae3 1/*
2 * html.c: implementation of html.h.
3 */
4
353bc75d 5#include "agedu.h"
70322ae3 6#include "html.h"
995db599 7#include "alloc.h"
70322ae3 8#include "trie.h"
9#include "index.h"
10
70322ae3 11#define MAXCOLOUR 511
12
13struct html {
14 char *buf;
15 size_t buflen, bufsize;
16 const void *t;
17 unsigned long long totalsize, oldest, newest;
18 char *path2;
19 char *href;
20 size_t hreflen;
21 const char *format;
22 unsigned long long thresholds[MAXCOLOUR-1];
23 time_t now;
24};
25
26static void vhtprintf(struct html *ctx, char *fmt, va_list ap)
27{
28 va_list ap2;
29 int size, size2;
50e82fdc 30 char testbuf[2];
70322ae3 31
32 va_copy(ap2, ap);
50e82fdc 33 /*
34 * Some C libraries (Solaris, I'm looking at you) don't like
35 * an output buffer size of zero in vsnprintf, but will return
36 * sensible values given any non-zero buffer size. Hence, we
37 * use testbuf to gauge the length of the string.
38 */
39 size = vsnprintf(testbuf, 1, fmt, ap2);
70322ae3 40 va_end(ap2);
41
42 if (ctx->buflen + size >= ctx->bufsize) {
43 ctx->bufsize = (ctx->buflen + size) * 3 / 2 + 1024;
44 ctx->buf = sresize(ctx->buf, ctx->bufsize, char);
45 }
46 size2 = vsnprintf(ctx->buf + ctx->buflen, ctx->bufsize - ctx->buflen,
47 fmt, ap);
48 assert(size == size2);
49 ctx->buflen += size;
50}
51
52static void htprintf(struct html *ctx, char *fmt, ...)
53{
54 va_list ap;
55 va_start(ap, fmt);
56 vhtprintf(ctx, fmt, ap);
57 va_end(ap);
58}
59
60static unsigned long long round_and_format_age(struct html *ctx,
61 unsigned long long age,
62 char *buf, int direction)
63{
64 struct tm tm, tm2;
65 char newbuf[80];
66 unsigned long long ret, newret;
67 int i;
68 int ym;
69 static const int minutes[] = { 5, 10, 15, 30, 45 };
70
71 tm = *localtime(&ctx->now);
72 ym = tm.tm_year * 12 + tm.tm_mon;
73
74 ret = ctx->now;
75 strcpy(buf, "Now");
76
77 for (i = 0; i < lenof(minutes); i++) {
78 newret = ctx->now - minutes[i] * 60;
79 sprintf(newbuf, "%d minutes", minutes[i]);
80 if (newret < age)
81 goto finish;
82 strcpy(buf, newbuf);
83 ret = newret;
84 }
85
86 for (i = 1; i < 24; i++) {
87 newret = ctx->now - i * (60*60);
88 sprintf(newbuf, "%d hour%s", i, i==1 ? "" : "s");
89 if (newret < age)
90 goto finish;
91 strcpy(buf, newbuf);
92 ret = newret;
93 }
94
95 for (i = 1; i < 7; i++) {
96 newret = ctx->now - i * (24*60*60);
97 sprintf(newbuf, "%d day%s", i, i==1 ? "" : "s");
98 if (newret < age)
99 goto finish;
100 strcpy(buf, newbuf);
101 ret = newret;
102 }
103
104 for (i = 1; i < 4; i++) {
105 newret = ctx->now - i * (7*24*60*60);
106 sprintf(newbuf, "%d week%s", i, i==1 ? "" : "s");
107 if (newret < age)
108 goto finish;
109 strcpy(buf, newbuf);
110 ret = newret;
111 }
112
113 for (i = 1; i < 11; i++) {
114 tm2 = tm; /* structure copy */
115 tm2.tm_year = (ym - i) / 12;
116 tm2.tm_mon = (ym - i) % 12;
117 newret = mktime(&tm2);
118 sprintf(newbuf, "%d month%s", i, i==1 ? "" : "s");
119 if (newret < age)
120 goto finish;
121 strcpy(buf, newbuf);
122 ret = newret;
123 }
124
125 for (i = 1;; i++) {
126 tm2 = tm; /* structure copy */
127 tm2.tm_year = (ym - i*12) / 12;
128 tm2.tm_mon = (ym - i*12) % 12;
129 newret = mktime(&tm2);
130 sprintf(newbuf, "%d year%s", i, i==1 ? "" : "s");
131 if (newret < age)
132 goto finish;
133 strcpy(buf, newbuf);
134 ret = newret;
135 }
136
137 finish:
138 if (direction > 0) {
139 /*
140 * Round toward newest, i.e. use the existing (buf,ret).
141 */
142 } else if (direction < 0) {
143 /*
144 * Round toward oldest, i.e. use (newbuf,newret);
145 */
146 strcpy(buf, newbuf);
147 ret = newret;
148 } else {
149 /*
150 * Round to nearest.
151 */
152 if (ret - age > age - newret) {
153 strcpy(buf, newbuf);
154 ret = newret;
155 }
156 }
157 return ret;
158}
159
160static void get_indices(const void *t, char *path,
161 unsigned long *xi1, unsigned long *xi2)
162{
163 size_t pathlen = strlen(path);
256c29a2 164 int c1 = path[pathlen], c2 = (pathlen > 0 ? path[pathlen-1] : 0);
70322ae3 165
166 *xi1 = trie_before(t, path);
256c29a2 167 make_successor(path);
70322ae3 168 *xi2 = trie_before(t, path);
256c29a2 169 path[pathlen] = c1;
170 if (pathlen > 0)
171 path[pathlen-1] = c2;
70322ae3 172}
173
174static unsigned long long fetch_size(const void *t, char *path,
175 unsigned long long atime)
176{
177 unsigned long xi1, xi2;
178
179 get_indices(t, path, &xi1, &xi2);
180
181 return index_query(t, xi2, atime) - index_query(t, xi1, atime);
182}
183
184static void htescape(struct html *ctx, const char *s, int n, int italics)
185{
186 while (n > 0 && *s) {
187 unsigned char c = (unsigned char)*s++;
188
189 if (c == '&')
190 htprintf(ctx, "&amp;");
191 else if (c == '<')
192 htprintf(ctx, "&lt;");
193 else if (c == '>')
194 htprintf(ctx, "&gt;");
195 else if (c >= ' ' && c < '\177')
196 htprintf(ctx, "%c", c);
197 else {
198 if (italics) htprintf(ctx, "<i>");
199 htprintf(ctx, "[%02x]", c);
200 if (italics) htprintf(ctx, "</i>");
201 }
202
203 n--;
204 }
205}
206
207static void begin_colour_bar(struct html *ctx)
208{
209 htprintf(ctx, "<table cellspacing=0 cellpadding=0"
210 " style=\"border:0\">\n<tr>\n");
211}
212
213static void add_to_colour_bar(struct html *ctx, int colour, int pixels)
214{
215 int r, g, b;
216 char buf[80];
217
218 if (colour >= 0 && colour < 256) /* red -> yellow fade */
219 r = 255, g = colour, b = 0;
220 else if (colour >= 256 && colour <= 511) /* yellow -> green fade */
221 r = 511 - colour, g = 255, b = 0;
222 else /* background grey */
223 r = g = b = 240;
224
225 if (colour < 0) {
226 /* no title text here */
227 } else if (colour == 0) {
228 strcpy(buf, "&lt; ");
229 round_and_format_age(ctx, ctx->thresholds[0], buf+5, 0);
230 } else if (colour == MAXCOLOUR) {
231 strcpy(buf, "&gt; ");
232 round_and_format_age(ctx, ctx->thresholds[MAXCOLOUR-1], buf+5, 0);
233 } else {
234 unsigned long long midrange =
235 (ctx->thresholds[colour] + ctx->thresholds[colour+1]) / 2;
236 round_and_format_age(ctx, midrange, buf, 0);
237 }
238
239 if (pixels > 0) {
240 htprintf(ctx, "<td style=\"width:%dpx; height:1em; "
241 "background-color:#%02x%02x%02x\"",
242 pixels, r, g, b);
243 if (colour >= 0)
244 htprintf(ctx, " title=\"%s\"", buf);
245 htprintf(ctx, "></td>\n");
246 }
247}
248
249static void end_colour_bar(struct html *ctx)
250{
251 htprintf(ctx, "</tr>\n</table>\n");
252}
253
254struct vector {
255 int want_href;
256 char *name;
257 unsigned long index;
258 unsigned long long sizes[MAXCOLOUR+1];
259};
260
261int vec_compare(const void *av, const void *bv)
262{
263 const struct vector *a = *(const struct vector **)av;
264 const struct vector *b = *(const struct vector **)bv;
265
266 if (a->sizes[MAXCOLOUR] > b->sizes[MAXCOLOUR])
267 return -1;
268 else if (a->sizes[MAXCOLOUR] < b->sizes[MAXCOLOUR])
269 return +1;
270 else if (a->want_href < b->want_href)
271 return +1;
272 else if (a->want_href > b->want_href)
273 return -1;
274 else if (a->want_href)
275 return strcmp(a->name, b->name);
276 else if (a->index < b->index)
277 return -1;
278 else if (a->index > b->index)
279 return +1;
280 return 0;
281}
282
283static struct vector *make_vector(struct html *ctx, char *path,
284 int want_href, char *name)
285{
286 unsigned long xi1, xi2;
287 struct vector *vec = snew(struct vector);
288 int i;
289
290 vec->want_href = want_href;
291 vec->name = name ? dupstr(name) : NULL;
292
293 get_indices(ctx->t, path, &xi1, &xi2);
294
295 vec->index = xi1;
296
297 for (i = 0; i <= MAXCOLOUR; i++) {
298 unsigned long long atime;
299 if (i == MAXCOLOUR)
300 atime = ULLONG_MAX;
301 else
302 atime = ctx->thresholds[i];
303 vec->sizes[i] = fetch_size(ctx->t, path, atime);
304 }
305
306 return vec;
307}
308
309static void print_heading(struct html *ctx, const char *title)
310{
311 htprintf(ctx, "<tr style=\"padding: 0.2em; background-color:#e0e0e0\">\n"
312 "<td colspan=4 align=center>%s</td>\n</tr>\n", title);
313}
314
315#define PIXEL_SIZE 600 /* FIXME: configurability? */
316static void write_report_line(struct html *ctx, struct vector *vec)
317{
742c1a74 318 unsigned long long size, asize, divisor;
70322ae3 319 int pix, newpix;
320 int i;
321
322 /*
010dd2a2 323 * A line with literally zero space usage should not be
324 * printed at all if it's a link to a subdirectory (since it
325 * probably means the whole thing was excluded by some
326 * --exclude-path wildcard). If it's [files] or the top-level
327 * line, though, we must always print _something_, and in that
328 * case we must fiddle about to prevent divisions by zero in
329 * the code below.
742c1a74 330 */
010dd2a2 331 if (!vec->sizes[MAXCOLOUR] && vec->want_href)
332 return;
742c1a74 333 divisor = ctx->totalsize;
010dd2a2 334 if (!divisor) {
742c1a74 335 divisor = 1;
010dd2a2 336 }
742c1a74 337
338 /*
70322ae3 339 * Find the total size of this subdirectory.
340 */
341 size = vec->sizes[MAXCOLOUR];
342 htprintf(ctx, "<tr>\n"
343 "<td style=\"padding: 0.2em; text-align: right\">%lluMb</td>\n",
84849cbd 344 ((size + ((1<<20)-1)) >> 20)); /* convert to Mb, rounding up */
70322ae3 345
346 /*
347 * Generate a colour bar.
348 */
349 htprintf(ctx, "<td style=\"padding: 0.2em\">\n");
350 begin_colour_bar(ctx);
351 pix = 0;
352 for (i = 0; i <= MAXCOLOUR; i++) {
353 asize = vec->sizes[i];
742c1a74 354 newpix = asize * PIXEL_SIZE / divisor;
70322ae3 355 add_to_colour_bar(ctx, i, newpix - pix);
356 pix = newpix;
357 }
358 add_to_colour_bar(ctx, -1, PIXEL_SIZE - pix);
359 end_colour_bar(ctx);
360 htprintf(ctx, "</td>\n");
361
362 /*
363 * Output size as a percentage of totalsize.
364 */
365 htprintf(ctx, "<td style=\"padding: 0.2em; text-align: right\">"
742c1a74 366 "%.2f%%</td>\n", (double)size / divisor * 100.0);
70322ae3 367
368 /*
369 * Output a subdirectory marker.
370 */
371 htprintf(ctx, "<td style=\"padding: 0.2em\">");
372 if (vec->name) {
373 int doing_href = 0;
374
375 if (ctx->format && vec->want_href) {
376 snprintf(ctx->href, ctx->hreflen, ctx->format, vec->index);
377 htprintf(ctx, "<a href=\"%s\">", ctx->href);
378 doing_href = 1;
379 }
380 htescape(ctx, vec->name, strlen(vec->name), 1);
381 if (doing_href)
382 htprintf(ctx, "</a>");
383 }
384 htprintf(ctx, "</td>\n</tr>\n");
385}
386
f2e52893 387char *html_query(const void *t, unsigned long index,
388 const struct html_config *cfg)
70322ae3 389{
390 struct html actx, *ctx = &actx;
391 char *path, *path2, *p, *q, *href;
392 char agebuf1[80], agebuf2[80];
256c29a2 393 size_t pathlen, subdirpos, hreflen;
70322ae3 394 unsigned long index2;
395 int i;
396 struct vector **vecs;
397 int nvecs, vecsize;
398 unsigned long xi1, xi2, xj1, xj2;
399
400 if (index >= trie_count(t))
401 return NULL;
402
403 ctx->buf = NULL;
404 ctx->buflen = ctx->bufsize = 0;
405 ctx->t = t;
f2e52893 406 ctx->format = cfg->format;
70322ae3 407 htprintf(ctx, "<html>\n");
408
409 path = snewn(1+trie_maxpathlen(t), char);
410 ctx->path2 = path2 = snewn(1+trie_maxpathlen(t), char);
f2e52893 411 if (cfg->format) {
412 hreflen = strlen(cfg->format) + 100;
70322ae3 413 href = snewn(hreflen, char);
414 } else {
415 hreflen = 0;
416 href = NULL;
417 }
418 ctx->hreflen = hreflen;
419 ctx->href = href;
420
421 /*
422 * HEAD section.
423 */
424 htprintf(ctx, "<head>\n");
425 trie_getpath(t, index, path);
bf53e756 426 htprintf(ctx, "<title>%s: ", PNAME);
70322ae3 427 htescape(ctx, path, strlen(path), 0);
428 htprintf(ctx, "</title>\n");
429 htprintf(ctx, "</head>\n");
430
431 /*
432 * Begin BODY section.
433 */
434 htprintf(ctx, "<body>\n");
435 htprintf(ctx, "<h3 align=center>Disk space breakdown by"
436 " last-access time</h3>\n");
437
438 /*
439 * Show the pathname we're centred on, with hyperlinks to
440 * parent directories where available.
441 */
442 htprintf(ctx, "<p align=center>\n<code>");
443 q = path;
cfe942fb 444 for (p = strchr(path, pathsep); p && p[1]; p = strchr(p, pathsep)) {
70322ae3 445 int doing_href = 0;
256c29a2 446 char c, *zp;
447
70322ae3 448 /*
449 * See if this path prefix exists in the trie. If so,
450 * generate a hyperlink.
451 */
256c29a2 452 zp = p;
453 if (p == path) /* special case for "/" at start */
454 zp++;
455
456 p++;
457
458 c = *zp;
459 *zp = '\0';
70322ae3 460 index2 = trie_before(t, path);
461 trie_getpath(t, index2, path2);
f2e52893 462 if (!strcmp(path, path2) && cfg->format) {
463 snprintf(href, hreflen, cfg->format, index2);
cfe942fb 464 if (!*href) /* special case that we understand */
465 strcpy(href, "./");
70322ae3 466 htprintf(ctx, "<a href=\"%s\">", href);
467 doing_href = 1;
468 }
256c29a2 469 *zp = c;
470 htescape(ctx, q, zp - q, 1);
70322ae3 471 if (doing_href)
472 htprintf(ctx, "</a>");
256c29a2 473 htescape(ctx, zp, p - zp, 1);
474 q = p;
70322ae3 475 }
476 htescape(ctx, q, strlen(q), 1);
477 htprintf(ctx, "</code>\n");
478
479 /*
480 * Decide on the age limit of our colour coding, establish the
481 * colour thresholds, and write out a key.
482 */
70322ae3 483 ctx->now = time(NULL);
f2e52893 484 if (cfg->autoage) {
485 ctx->oldest = index_order_stat(t, 0.05);
486 ctx->newest = index_order_stat(t, 1.0);
487 ctx->oldest = round_and_format_age(ctx, ctx->oldest, agebuf1, -1);
488 ctx->newest = round_and_format_age(ctx, ctx->newest, agebuf2, +1);
489 } else {
490 ctx->oldest = cfg->oldest;
491 ctx->newest = cfg->newest;
492 ctx->oldest = round_and_format_age(ctx, ctx->oldest, agebuf1, 0);
493 ctx->newest = round_and_format_age(ctx, ctx->newest, agebuf2, 0);
494 }
70322ae3 495 for (i = 0; i < MAXCOLOUR-1; i++) {
496 ctx->thresholds[i] =
497 ctx->oldest + (ctx->newest - ctx->oldest) * i / MAXCOLOUR;
498 }
499 htprintf(ctx, "<p align=center>Key to colour coding (mouse over for more detail):\n");
500 htprintf(ctx, "<p align=center style=\"padding: 0; margin-top:0.4em; "
501 "margin-bottom:1em\"");
502 begin_colour_bar(ctx);
503 htprintf(ctx, "<td style=\"padding-right:1em\">%s</td>\n", agebuf1);
504 for (i = 0; i < MAXCOLOUR; i++)
505 add_to_colour_bar(ctx, i, 1);
506 htprintf(ctx, "<td style=\"padding-left:1em\">%s</td>\n", agebuf2);
507 end_colour_bar(ctx);
508
509 /*
510 * Begin the main table.
511 */
512 htprintf(ctx, "<p align=center>\n<table style=\"margin:0; border:0\">\n");
513
514 /*
515 * Find the total size of our entire subdirectory. We'll use
516 * that as the scale for all the colour bars in this report.
517 */
518 ctx->totalsize = fetch_size(t, path, ULLONG_MAX);
519
520 /*
521 * Generate a report line for the whole subdirectory.
522 */
523 vecsize = 64;
524 vecs = snewn(vecsize, struct vector *);
525 nvecs = 1;
526 vecs[0] = make_vector(ctx, path, 0, NULL);
527 print_heading(ctx, "Overall");
528 write_report_line(ctx, vecs[0]);
529
530 /*
531 * Now generate report lines for all its children, and the
532 * files contained in it.
533 */
534 print_heading(ctx, "Subdirectories");
535
536 vecs[0]->name = dupstr("[files]");
537 get_indices(t, path, &xi1, &xi2);
538 xi1++;
539 pathlen = strlen(path);
256c29a2 540 subdirpos = pathlen + 1;
541 if (pathlen > 0 && path[pathlen-1] == pathsep)
542 subdirpos--;
70322ae3 543 while (xi1 < xi2) {
544 trie_getpath(t, xi1, path2);
545 get_indices(t, ctx->path2, &xj1, &xj2);
546 xi1 = xj2;
547 if (xj2 - xj1 <= 1)
548 continue; /* skip individual files */
549 if (nvecs >= vecsize) {
550 vecsize = nvecs * 3 / 2 + 64;
551 vecs = sresize(vecs, vecsize, struct vector *);
552 }
553 assert(strlen(path2) > pathlen);
256c29a2 554 vecs[nvecs] = make_vector(ctx, path2, 1, path2 + subdirpos);
70322ae3 555 for (i = 0; i <= MAXCOLOUR; i++)
556 vecs[0]->sizes[i] -= vecs[nvecs]->sizes[i];
557 nvecs++;
558 }
559
560 qsort(vecs, nvecs, sizeof(vecs[0]), vec_compare);
561
562 for (i = 0; i < nvecs; i++)
563 write_report_line(ctx, vecs[i]);
564
565 /*
566 * Close the main table.
567 */
568 htprintf(ctx, "</table>\n");
569
570 /*
571 * Finish up and tidy up.
572 */
573 htprintf(ctx, "</body>\n");
574 htprintf(ctx, "</html>\n");
575 sfree(href);
576 sfree(path2);
577 sfree(path);
578 for (i = 0; i < nvecs; i++) {
579 sfree(vecs[i]->name);
580 sfree(vecs[i]);
581 }
582 sfree(vecs);
583
584 return ctx->buf;
585}