2 * html.c: implementation of html.h.
15 size_t buflen
, bufsize
;
17 unsigned long long totalsize
, oldest
, newest
;
22 unsigned long long thresholds
[MAXCOLOUR
];
23 char *titletexts
[MAXCOLOUR
+1];
27 static void vhtprintf(struct html
*ctx
, char *fmt
, va_list ap
)
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.
40 size
= vsnprintf(testbuf
, 1, fmt
, ap2
);
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);
47 size2
= vsnprintf(ctx
->buf
+ ctx
->buflen
, ctx
->bufsize
- ctx
->buflen
,
49 assert(size
== size2
);
53 static void htprintf(struct html
*ctx
, char *fmt
, ...)
57 vhtprintf(ctx
, fmt
, ap
);
61 static unsigned long long round_and_format_age(struct html
*ctx
,
62 unsigned long long age
,
63 char *buf
, int direction
)
67 unsigned long long ret
, newret
;
70 static const int minutes
[] = { 5, 10, 15, 30, 45 };
72 tm
= *localtime(&ctx
->now
);
73 ym
= tm
.tm_year
* 12 + tm
.tm_mon
;
78 for (i
= 0; i
< lenof(minutes
); i
++) {
79 newret
= ctx
->now
- minutes
[i
] * 60;
80 sprintf(newbuf
, "%d minutes", minutes
[i
]);
87 for (i
= 1; i
< 24; i
++) {
88 newret
= ctx
->now
- i
* (60*60);
89 sprintf(newbuf
, "%d hour%s", i
, i
==1 ?
"" : "s");
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");
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");
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");
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");
141 * Round toward newest, i.e. use the existing (buf,ret).
143 } else if (direction
< 0) {
145 * Round toward oldest, i.e. use (newbuf,newret);
153 if (ret
- age
> age
- newret
) {
161 static void get_indices(const void *t
, char *path
,
162 unsigned long *xi1
, unsigned long *xi2
)
164 size_t pathlen
= strlen(path
);
165 int c1
= path
[pathlen
], c2
= (pathlen
> 0 ? path
[pathlen
-1] : 0);
167 *xi1
= trie_before(t
, path
);
168 make_successor(path
);
169 *xi2
= trie_before(t
, path
);
172 path
[pathlen
-1] = c2
;
175 static unsigned long long fetch_size(const void *t
,
176 unsigned long xi1
, unsigned long xi2
,
177 unsigned long long atime
)
179 if (xi2
- xi1
== 1) {
181 * We are querying an individual file, so we should not
182 * depend on the index entries either side of the node,
183 * since they almost certainly don't both exist. Instead,
184 * just look up the file's size and atime in the main trie.
186 const struct trie_file
*f
= trie_getfile(t
, xi1
);
187 if (f
->atime
< atime
)
192 return index_query(t
, xi2
, atime
) - index_query(t
, xi1
, atime
);
196 static void htescape(struct html
*ctx
, const char *s
, int n
, int italics
)
198 while (n
> 0 && *s
) {
199 unsigned char c
= (unsigned char)*s
++;
202 htprintf(ctx
, "&");
204 htprintf(ctx
, "<");
206 htprintf(ctx
, ">");
207 else if (c
>= ' ' && c
< '\177')
208 htprintf(ctx
, "%c", c
);
210 if (italics
) htprintf(ctx
, "<i>");
211 htprintf(ctx
, "[%02x]", c
);
212 if (italics
) htprintf(ctx
, "</i>");
219 static void begin_colour_bar(struct html
*ctx
)
221 htprintf(ctx
, "<table cellspacing=0 cellpadding=0"
222 " style=\"border:0\">\n<tr>\n");
225 static void add_to_colour_bar(struct html
*ctx
, int colour
, int pixels
)
229 if (colour
>= 0 && colour
< 256) /* red -> yellow fade */
230 r
= 255, g
= colour
, b
= 0;
231 else if (colour
>= 256 && colour
<= 511) /* yellow -> green fade */
232 r
= 511 - colour
, g
= 255, b
= 0;
233 else /* background grey */
237 htprintf(ctx
, "<td style=\"width:%dpx; height:1em; "
238 "background-color:#%02x%02x%02x\"",
241 htprintf(ctx
, " title=\"%s\"", ctx
->titletexts
[colour
]);
242 htprintf(ctx
, "></td>\n");
246 static void end_colour_bar(struct html
*ctx
)
248 htprintf(ctx
, "</tr>\n</table>\n");
252 int want_href
, essential
;
254 int literal
; /* should the name be formatted in fixed-pitch? */
256 unsigned long long sizes
[MAXCOLOUR
+1];
259 int vec_compare(const void *av
, const void *bv
)
261 const struct vector
*a
= *(const struct vector
**)av
;
262 const struct vector
*b
= *(const struct vector
**)bv
;
264 if (a
->sizes
[MAXCOLOUR
] > b
->sizes
[MAXCOLOUR
])
266 else if (a
->sizes
[MAXCOLOUR
] < b
->sizes
[MAXCOLOUR
])
268 else if (a
->want_href
< b
->want_href
)
270 else if (a
->want_href
> b
->want_href
)
272 else if (a
->want_href
)
273 return strcmp(a
->name
, b
->name
);
274 else if (a
->index
< b
->index
)
276 else if (a
->index
> b
->index
)
278 else if (a
->essential
< b
->essential
)
280 else if (a
->essential
> b
->essential
)
285 static struct vector
*make_vector(struct html
*ctx
, char *path
,
286 int want_href
, int essential
,
287 char *name
, int literal
)
289 unsigned long xi1
, xi2
;
290 struct vector
*vec
= snew(struct vector
);
293 vec
->want_href
= want_href
;
294 vec
->essential
= essential
;
295 vec
->name
= name ?
dupstr(name
) : NULL
;
296 vec
->literal
= literal
;
298 get_indices(ctx
->t
, path
, &xi1
, &xi2
);
302 for (i
= 0; i
<= MAXCOLOUR
; i
++) {
303 unsigned long long atime
;
307 atime
= ctx
->thresholds
[i
];
308 vec
->sizes
[i
] = fetch_size(ctx
->t
, xi1
, xi2
, atime
);
314 static void print_heading(struct html
*ctx
, const char *title
)
316 htprintf(ctx
, "<tr style=\"padding: 0.2em; background-color:#e0e0e0\">\n"
317 "<td colspan=4 align=center>%s</td>\n</tr>\n", title
);
320 #define PIXEL_SIZE 600 /* FIXME: configurability? */
321 static void write_report_line(struct html
*ctx
, struct vector
*vec
)
323 unsigned long long size
, asize
, divisor
;
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
336 if (!vec
->sizes
[MAXCOLOUR
] && !vec
->essential
)
338 divisor
= ctx
->totalsize
;
344 * Find the total size of this subdirectory.
346 size
= vec
->sizes
[MAXCOLOUR
];
347 htprintf(ctx
, "<tr>\n"
348 "<td style=\"padding: 0.2em; text-align: right\">%lluMb</td>\n",
349 ((size
+ ((1<<20)-1)) >> 20)); /* convert to Mb, rounding up */
352 * Generate a colour bar.
354 htprintf(ctx
, "<td style=\"padding: 0.2em\">\n");
355 begin_colour_bar(ctx
);
357 for (i
= 0; i
<= MAXCOLOUR
; i
++) {
358 asize
= vec
->sizes
[i
];
359 newpix
= asize
* PIXEL_SIZE
/ divisor
;
360 add_to_colour_bar(ctx
, i
, newpix
- pix
);
363 add_to_colour_bar(ctx
, -1, PIXEL_SIZE
- pix
);
365 htprintf(ctx
, "</td>\n");
368 * Output size as a percentage of totalsize.
370 htprintf(ctx
, "<td style=\"padding: 0.2em; text-align: right\">"
371 "%.2f%%</td>\n", (double)size
/ divisor
* 100.0);
374 * Output a subdirectory marker.
376 htprintf(ctx
, "<td style=\"padding: 0.2em\">");
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
);
386 htprintf(ctx
, "<code>");
387 htescape(ctx
, vec
->name
, strlen(vec
->name
), 1);
389 htprintf(ctx
, "</code>");
391 htprintf(ctx
, "</a>");
393 htprintf(ctx
, "</td>\n</tr>\n");
396 int strcmptrailingpathsep(const char *a
, const char *b
)
398 while (*a
== *b
&& *a
)
401 if ((*a
== pathsep
&& !a
[1] && !*b
) ||
402 (*b
== pathsep
&& !b
[1] && !*a
))
405 return (int)(unsigned char)*a
- (int)(unsigned char)*b
;
408 char *html_query(const void *t
, unsigned long index
,
409 const struct html_config
*cfg
)
411 struct html actx
, *ctx
= &actx
;
412 char *path
, *path2
, *p
, *q
, *href
;
413 char agebuf1
[80], agebuf2
[80];
414 size_t pathlen
, subdirpos
, hreflen
;
415 unsigned long index2
;
417 struct vector
**vecs
;
419 unsigned long xi1
, xi2
, xj1
, xj2
;
421 if (index
>= trie_count(t
))
425 ctx
->buflen
= ctx
->bufsize
= 0;
427 ctx
->format
= cfg
->format
;
428 htprintf(ctx
, "<html>\n");
430 path
= snewn(1+trie_maxpathlen(t
), char);
431 ctx
->path2
= path2
= snewn(1+trie_maxpathlen(t
), char);
433 hreflen
= strlen(cfg
->format
) + 100;
434 href
= snewn(hreflen
, char);
439 ctx
->hreflen
= hreflen
;
445 htprintf(ctx
, "<head>\n");
446 trie_getpath(t
, index
, path
);
447 htprintf(ctx
, "<title>%s: ", PNAME
);
448 htescape(ctx
, path
, strlen(path
), 0);
449 htprintf(ctx
, "</title>\n");
450 htprintf(ctx
, "</head>\n");
453 * Begin BODY section.
455 htprintf(ctx
, "<body>\n");
456 htprintf(ctx
, "<h3 align=center>Disk space breakdown by"
457 " last-access time</h3>\n");
460 * Show the pathname we're centred on, with hyperlinks to
461 * parent directories where available.
463 htprintf(ctx
, "<p align=center>\n<code>");
465 for (p
= strchr(path
, pathsep
); p
&& p
[1]; p
= strchr(p
, pathsep
)) {
470 * See if this path prefix exists in the trie. If so,
471 * generate a hyperlink.
474 if (p
== path
) /* special case for "/" at start */
481 index2
= trie_before(t
, path
);
482 trie_getpath(t
, index2
, path2
);
483 if (!strcmptrailingpathsep(path
, path2
) && cfg
->format
) {
484 snprintf(href
, hreflen
, cfg
->format
, index2
);
485 if (!*href
) /* special case that we understand */
487 htprintf(ctx
, "<a href=\"%s\">", href
);
491 htescape(ctx
, q
, zp
- q
, 1);
493 htprintf(ctx
, "</a>");
494 htescape(ctx
, zp
, p
- zp
, 1);
497 htescape(ctx
, q
, strlen(q
), 1);
498 htprintf(ctx
, "</code>\n");
501 * Decide on the age limit of our colour coding, establish the
502 * colour thresholds, and write out a key.
504 ctx
->now
= time(NULL
);
506 ctx
->oldest
= index_order_stat(t
, 0.05);
507 ctx
->newest
= index_order_stat(t
, 1.0);
508 ctx
->oldest
= round_and_format_age(ctx
, ctx
->oldest
, agebuf1
, -1);
509 ctx
->newest
= round_and_format_age(ctx
, ctx
->newest
, agebuf2
, +1);
511 ctx
->oldest
= cfg
->oldest
;
512 ctx
->newest
= cfg
->newest
;
513 ctx
->oldest
= round_and_format_age(ctx
, ctx
->oldest
, agebuf1
, 0);
514 ctx
->newest
= round_and_format_age(ctx
, ctx
->newest
, agebuf2
, 0);
516 for (i
= 0; i
< MAXCOLOUR
; i
++) {
518 ctx
->oldest
+ (ctx
->newest
- ctx
->oldest
) * i
/ (MAXCOLOUR
-1);
520 for (i
= 0; i
<= MAXCOLOUR
; i
++) {
524 strcpy(buf
, "< ");
525 round_and_format_age(ctx
, ctx
->thresholds
[0], buf
+5, 0);
526 } else if (i
== MAXCOLOUR
) {
527 strcpy(buf
, "> ");
528 round_and_format_age(ctx
, ctx
->thresholds
[MAXCOLOUR
-1], buf
+5, 0);
530 unsigned long long midrange
=
531 (ctx
->thresholds
[i
-1] + ctx
->thresholds
[i
]) / 2;
532 round_and_format_age(ctx
, midrange
, buf
, 0);
535 ctx
->titletexts
[i
] = dupstr(buf
);
537 htprintf(ctx
, "<p align=center>Key to colour coding (mouse over for more detail):\n");
538 htprintf(ctx
, "<p align=center style=\"padding: 0; margin-top:0.4em; "
539 "margin-bottom:1em\"");
540 begin_colour_bar(ctx
);
541 htprintf(ctx
, "<td style=\"padding-right:1em\">%s</td>\n", agebuf1
);
542 for (i
= 0; i
< MAXCOLOUR
; i
++)
543 add_to_colour_bar(ctx
, i
, 1);
544 htprintf(ctx
, "<td style=\"padding-left:1em\">%s</td>\n", agebuf2
);
548 * Begin the main table.
550 htprintf(ctx
, "<p align=center>\n<table style=\"margin:0; border:0\">\n");
553 * Find the total size of our entire subdirectory. We'll use
554 * that as the scale for all the colour bars in this report.
556 get_indices(t
, path
, &xi1
, &xi2
);
557 ctx
->totalsize
= fetch_size(t
, xi1
, xi2
, ULLONG_MAX
);
560 * Generate a report line for the whole subdirectory.
563 vecs
= snewn(vecsize
, struct vector
*);
565 vecs
[0] = make_vector(ctx
, path
, 0, 1, NULL
, 0);
566 print_heading(ctx
, "Overall");
567 write_report_line(ctx
, vecs
[0]);
570 * Now generate report lines for all its children, and the
571 * files contained in it.
573 print_heading(ctx
, "Subdirectories");
575 vecs
[0]->name
= dupstr("[files]");
576 get_indices(t
, path
, &xi1
, &xi2
);
578 pathlen
= strlen(path
);
579 subdirpos
= pathlen
+ 1;
580 if (pathlen
> 0 && path
[pathlen
-1] == pathsep
)
583 trie_getpath(t
, xi1
, path2
);
584 get_indices(t
, ctx
->path2
, &xj1
, &xj2
);
586 if (!cfg
->showfiles
&& xj2
- xj1
<= 1)
587 continue; /* skip individual files */
588 if (nvecs
>= vecsize
) {
589 vecsize
= nvecs
* 3 / 2 + 64;
590 vecs
= sresize(vecs
, vecsize
, struct vector
*);
592 assert(strlen(path2
) > pathlen
);
593 vecs
[nvecs
] = make_vector(ctx
, path2
, (xj2
- xj1
> 1), 0,
594 path2
+ subdirpos
, 1);
595 for (i
= 0; i
<= MAXCOLOUR
; i
++)
596 vecs
[0]->sizes
[i
] -= vecs
[nvecs
]->sizes
[i
];
600 qsort(vecs
, nvecs
, sizeof(vecs
[0]), vec_compare
);
602 for (i
= 0; i
< nvecs
; i
++)
603 write_report_line(ctx
, vecs
[i
]);
606 * Close the main table.
608 htprintf(ctx
, "</table>\n");
611 * Finish up and tidy up.
613 htprintf(ctx
, "</body>\n");
614 htprintf(ctx
, "</html>\n");
618 for (i
= 0; i
< nvecs
; i
++) {
619 sfree(vecs
[i
]->name
);