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