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