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