* html.c: implementation of html.h.
*/
-#include <assert.h>
-#include <stddef.h>
-#include <string.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include <time.h>
-
+#include "agedu.h"
#include "html.h"
-#include "malloc.h"
+#include "alloc.h"
#include "trie.h"
#include "index.h"
-#define lenof(x) ( sizeof((x)) / sizeof(*(x)) )
-
#define MAXCOLOUR 511
-extern char pathsep;
-
struct html {
char *buf;
size_t buflen, bufsize;
char *path2;
char *href;
size_t hreflen;
- const char *format;
- unsigned long long thresholds[MAXCOLOUR-1];
+ const char *format, *rootpage;
+ unsigned long long thresholds[MAXCOLOUR];
+ char *titletexts[MAXCOLOUR+1];
time_t now;
};
-static void vhtprintf(struct html *ctx, char *fmt, va_list ap)
+static void vhtprintf(struct html *ctx, const char *fmt, va_list ap)
{
va_list ap2;
int size, size2;
+ char testbuf[2];
va_copy(ap2, ap);
- size = vsnprintf(NULL, 0, fmt, ap2);
+ /*
+ * Some C libraries (Solaris, I'm looking at you) don't like
+ * an output buffer size of zero in vsnprintf, but will return
+ * sensible values given any non-zero buffer size. Hence, we
+ * use testbuf to gauge the length of the string.
+ */
+ size = vsnprintf(testbuf, 1, fmt, ap2);
va_end(ap2);
if (ctx->buflen + size >= ctx->bufsize) {
ctx->buflen += size;
}
-static void htprintf(struct html *ctx, char *fmt, ...)
+static void htprintf(struct html *ctx, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
unsigned long *xi1, unsigned long *xi2)
{
size_t pathlen = strlen(path);
+ int c1 = path[pathlen], c2 = (pathlen > 0 ? path[pathlen-1] : 0);
*xi1 = trie_before(t, path);
- path[pathlen] = '\001';
- path[pathlen+1] = '\0';
+ make_successor(path);
*xi2 = trie_before(t, path);
- path[pathlen] = '\0';
+ path[pathlen] = c1;
+ if (pathlen > 0)
+ path[pathlen-1] = c2;
}
-static unsigned long long fetch_size(const void *t, char *path,
+static unsigned long long fetch_size(const void *t,
+ unsigned long xi1, unsigned long xi2,
unsigned long long atime)
{
- unsigned long xi1, xi2;
-
- get_indices(t, path, &xi1, &xi2);
-
- return index_query(t, xi2, atime) - index_query(t, xi1, atime);
+ if (xi2 - xi1 == 1) {
+ /*
+ * We are querying an individual file, so we should not
+ * depend on the index entries either side of the node,
+ * since they almost certainly don't both exist. Instead,
+ * just look up the file's size and atime in the main trie.
+ */
+ const struct trie_file *f = trie_getfile(t, xi1);
+ if (f->atime < atime)
+ return f->size;
+ else
+ return 0;
+ } else {
+ return index_query(t, xi2, atime) - index_query(t, xi1, atime);
+ }
}
static void htescape(struct html *ctx, const char *s, int n, int italics)
static void add_to_colour_bar(struct html *ctx, int colour, int pixels)
{
int r, g, b;
- char buf[80];
if (colour >= 0 && colour < 256) /* red -> yellow fade */
r = 255, g = colour, b = 0;
else /* background grey */
r = g = b = 240;
- if (colour < 0) {
- /* no title text here */
- } else if (colour == 0) {
- strcpy(buf, "< ");
- round_and_format_age(ctx, ctx->thresholds[0], buf+5, 0);
- } else if (colour == MAXCOLOUR) {
- strcpy(buf, "> ");
- round_and_format_age(ctx, ctx->thresholds[MAXCOLOUR-1], buf+5, 0);
- } else {
- unsigned long long midrange =
- (ctx->thresholds[colour] + ctx->thresholds[colour+1]) / 2;
- round_and_format_age(ctx, midrange, buf, 0);
- }
-
if (pixels > 0) {
htprintf(ctx, "<td style=\"width:%dpx; height:1em; "
"background-color:#%02x%02x%02x\"",
pixels, r, g, b);
if (colour >= 0)
- htprintf(ctx, " title=\"%s\"", buf);
+ htprintf(ctx, " title=\"%s\"", ctx->titletexts[colour]);
htprintf(ctx, "></td>\n");
}
}
}
struct vector {
- int want_href;
+ int want_href, essential;
char *name;
+ int literal; /* should the name be formatted in fixed-pitch? */
unsigned long index;
unsigned long long sizes[MAXCOLOUR+1];
};
return -1;
else if (a->index > b->index)
return +1;
+ else if (a->essential < b->essential)
+ return +1;
+ else if (a->essential > b->essential)
+ return -1;
return 0;
}
static struct vector *make_vector(struct html *ctx, char *path,
- int want_href, char *name)
+ int want_href, int essential,
+ char *name, int literal)
{
unsigned long xi1, xi2;
struct vector *vec = snew(struct vector);
int i;
vec->want_href = want_href;
+ vec->essential = essential;
vec->name = name ? dupstr(name) : NULL;
+ vec->literal = literal;
get_indices(ctx->t, path, &xi1, &xi2);
atime = ULLONG_MAX;
else
atime = ctx->thresholds[i];
- vec->sizes[i] = fetch_size(ctx->t, path, atime);
+ vec->sizes[i] = fetch_size(ctx->t, xi1, xi2, atime);
}
return vec;
"<td colspan=4 align=center>%s</td>\n</tr>\n", title);
}
+static void compute_display_size(unsigned long long size,
+ const char **fmt, double *display_size)
+{
+ static const char *const fmts[] = {
+ "%g b", "%g Kb", "%#.1f Mb", "%#.1f Gb", "%#.1f Tb",
+ "%#.1f Pb", "%#.1f Eb", "%#.1f Zb", "%#.1f Yb"
+ };
+ int shift = 0;
+ unsigned long long tmpsize;
+ double denominator;
+
+ tmpsize = size;
+ denominator = 1.0;
+ while (tmpsize >= 1024 && shift < lenof(fmts)-1) {
+ tmpsize >>= 10;
+ denominator *= 1024.0;
+ shift++;
+ }
+ *display_size = size / denominator;
+ *fmt = fmts[shift];
+}
+
+static void make_filename(char *buf, size_t buflen,
+ const char *format, const char *rootpage,
+ unsigned long index)
+{
+ if (index == 0 && rootpage)
+ snprintf(buf, buflen, "%s", rootpage);
+ else
+ snprintf(buf, buflen, format, index);
+}
+
#define PIXEL_SIZE 600 /* FIXME: configurability? */
static void write_report_line(struct html *ctx, struct vector *vec)
{
unsigned long long size, asize, divisor;
+ double display_size;
int pix, newpix;
int i;
+ const char *unitsfmt;
/*
* A line with literally zero space usage should not be
* case we must fiddle about to prevent divisions by zero in
* the code below.
*/
- if (!vec->sizes[MAXCOLOUR] && vec->want_href)
+ if (!vec->sizes[MAXCOLOUR] && !vec->essential)
return;
divisor = ctx->totalsize;
if (!divisor) {
* Find the total size of this subdirectory.
*/
size = vec->sizes[MAXCOLOUR];
+ compute_display_size(size, &unitsfmt, &display_size);
htprintf(ctx, "<tr>\n"
- "<td style=\"padding: 0.2em; text-align: right\">%lluMb</td>\n",
- ((size + ((1<<20)-1)) >> 20)); /* convert to Mb, rounding up */
+ "<td style=\"padding: 0.2em; text-align: right\">");
+ htprintf(ctx, unitsfmt, display_size);
+ htprintf(ctx, "</td>\n");
/*
* Generate a colour bar.
int doing_href = 0;
if (ctx->format && vec->want_href) {
- snprintf(ctx->href, ctx->hreflen, ctx->format, vec->index);
+ make_filename(ctx->href, ctx->hreflen,
+ ctx->format, ctx->rootpage,
+ vec->index);
htprintf(ctx, "<a href=\"%s\">", ctx->href);
doing_href = 1;
}
+ if (vec->literal)
+ htprintf(ctx, "<code>");
htescape(ctx, vec->name, strlen(vec->name), 1);
+ if (vec->literal)
+ htprintf(ctx, "</code>");
if (doing_href)
htprintf(ctx, "</a>");
}
htprintf(ctx, "</td>\n</tr>\n");
}
+int strcmptrailingpathsep(const char *a, const char *b)
+{
+ while (*a == *b && *a)
+ a++, b++;
+
+ if ((*a == pathsep && !a[1] && !*b) ||
+ (*b == pathsep && !b[1] && !*a))
+ return 0;
+
+ return (int)(unsigned char)*a - (int)(unsigned char)*b;
+}
+
char *html_query(const void *t, unsigned long index,
- const struct html_config *cfg)
+ const struct html_config *cfg, int downlink)
{
struct html actx, *ctx = &actx;
char *path, *path2, *p, *q, *href;
char agebuf1[80], agebuf2[80];
- size_t pathlen, hreflen;
+ size_t pathlen, subdirpos, hreflen;
unsigned long index2;
int i;
struct vector **vecs;
ctx->buflen = ctx->bufsize = 0;
ctx->t = t;
ctx->format = cfg->format;
+ ctx->rootpage = cfg->rootpage;
htprintf(ctx, "<html>\n");
path = snewn(1+trie_maxpathlen(t), char);
*/
htprintf(ctx, "<head>\n");
trie_getpath(t, index, path);
- htprintf(ctx, "<title>agedu: ");
+ htprintf(ctx, "<title>%s: ", PNAME);
htescape(ctx, path, strlen(path), 0);
htprintf(ctx, "</title>\n");
htprintf(ctx, "</head>\n");
*/
htprintf(ctx, "<p align=center>\n<code>");
q = path;
- for (p = strchr(path, pathsep); p; p = strchr(p+1, pathsep)) {
+ for (p = strchr(path, pathsep); p && p[1]; p = strchr(p, pathsep)) {
int doing_href = 0;
+ char c, *zp;
+
/*
* See if this path prefix exists in the trie. If so,
* generate a hyperlink.
*/
- *p = '\0';
+ zp = p;
+ if (p == path) /* special case for "/" at start */
+ zp++;
+
+ p++;
+
+ c = *zp;
+ *zp = '\0';
index2 = trie_before(t, path);
trie_getpath(t, index2, path2);
- if (!strcmp(path, path2) && cfg->format) {
- snprintf(href, hreflen, cfg->format, index2);
+ if (!strcmptrailingpathsep(path, path2) && cfg->format) {
+ make_filename(href, hreflen, cfg->format, cfg->rootpage, index2);
+ if (!*href) /* special case that we understand */
+ strcpy(href, "./");
htprintf(ctx, "<a href=\"%s\">", href);
doing_href = 1;
}
- *p = pathsep;
- htescape(ctx, q, p - q, 1);
- q = p + 1;
+ *zp = c;
+ htescape(ctx, q, zp - q, 1);
if (doing_href)
htprintf(ctx, "</a>");
- htescape(ctx, p, 1, 1);
+ htescape(ctx, zp, p - zp, 1);
+ q = p;
}
htescape(ctx, q, strlen(q), 1);
htprintf(ctx, "</code>\n");
ctx->oldest = round_and_format_age(ctx, ctx->oldest, agebuf1, 0);
ctx->newest = round_and_format_age(ctx, ctx->newest, agebuf2, 0);
}
- for (i = 0; i < MAXCOLOUR-1; i++) {
+ for (i = 0; i < MAXCOLOUR; i++) {
ctx->thresholds[i] =
- ctx->oldest + (ctx->newest - ctx->oldest) * i / MAXCOLOUR;
+ ctx->oldest + (ctx->newest - ctx->oldest) * i / (MAXCOLOUR-1);
+ }
+ for (i = 0; i <= MAXCOLOUR; i++) {
+ char buf[80];
+
+ if (i == 0) {
+ strcpy(buf, "< ");
+ round_and_format_age(ctx, ctx->thresholds[0], buf+5, 0);
+ } else if (i == MAXCOLOUR) {
+ strcpy(buf, "> ");
+ round_and_format_age(ctx, ctx->thresholds[MAXCOLOUR-1], buf+5, 0);
+ } else {
+ unsigned long long midrange =
+ (ctx->thresholds[i-1] + ctx->thresholds[i]) / 2;
+ round_and_format_age(ctx, midrange, buf, 0);
+ }
+
+ ctx->titletexts[i] = dupstr(buf);
}
htprintf(ctx, "<p align=center>Key to colour coding (mouse over for more detail):\n");
htprintf(ctx, "<p align=center style=\"padding: 0; margin-top:0.4em; "
- "margin-bottom:1em\"");
+ "margin-bottom:1em\">");
begin_colour_bar(ctx);
htprintf(ctx, "<td style=\"padding-right:1em\">%s</td>\n", agebuf1);
for (i = 0; i < MAXCOLOUR; i++)
* Find the total size of our entire subdirectory. We'll use
* that as the scale for all the colour bars in this report.
*/
- ctx->totalsize = fetch_size(t, path, ULLONG_MAX);
+ get_indices(t, path, &xi1, &xi2);
+ ctx->totalsize = fetch_size(t, xi1, xi2, ULLONG_MAX);
/*
* Generate a report line for the whole subdirectory.
vecsize = 64;
vecs = snewn(vecsize, struct vector *);
nvecs = 1;
- vecs[0] = make_vector(ctx, path, 0, NULL);
+ vecs[0] = make_vector(ctx, path, 0, 1, NULL, 0);
print_heading(ctx, "Overall");
write_report_line(ctx, vecs[0]);
get_indices(t, path, &xi1, &xi2);
xi1++;
pathlen = strlen(path);
+ subdirpos = pathlen + 1;
+ if (pathlen > 0 && path[pathlen-1] == pathsep)
+ subdirpos--;
while (xi1 < xi2) {
trie_getpath(t, xi1, path2);
get_indices(t, ctx->path2, &xj1, &xj2);
xi1 = xj2;
- if (xj2 - xj1 <= 1)
+ if (!cfg->showfiles && xj2 - xj1 <= 1)
continue; /* skip individual files */
if (nvecs >= vecsize) {
vecsize = nvecs * 3 / 2 + 64;
vecs = sresize(vecs, vecsize, struct vector *);
}
assert(strlen(path2) > pathlen);
- vecs[nvecs] = make_vector(ctx, path2, 1, path2 + pathlen + 1);
+ vecs[nvecs] = make_vector(ctx, path2, downlink && (xj2 - xj1 > 1), 0,
+ path2 + subdirpos, 1);
for (i = 0; i <= MAXCOLOUR; i++)
vecs[0]->sizes[i] -= vecs[nvecs]->sizes[i];
nvecs++;
return ctx->buf;
}
+
+int html_dump(const void *t, unsigned long index, unsigned long endindex,
+ int maxdepth, const struct html_config *cfg,
+ const char *pathprefix)
+{
+ /*
+ * Determine the filename for this file.
+ */
+ assert(cfg->format != NULL);
+ int prefixlen = strlen(pathprefix);
+ int fnmax = strlen(pathprefix) + strlen(cfg->format) + 100;
+ char filename[fnmax];
+ strcpy(filename, pathprefix);
+ make_filename(filename + prefixlen, fnmax - prefixlen,
+ cfg->format, cfg->rootpage, index);
+
+ /*
+ * Create the HTML itself. Don't write out downlinks from our
+ * deepest level.
+ */
+ char *html = html_query(t, index, cfg, maxdepth != 0);
+
+ /*
+ * Write it out.
+ */
+ FILE *fp = fopen(filename, "w");
+ if (!fp) {
+ fprintf(stderr, "%s: %s: open: %s\n", PNAME,
+ filename, strerror(errno));
+ return 1;
+ }
+ if (fputs(html, fp) < 0) {
+ fprintf(stderr, "%s: %s: write: %s\n", PNAME,
+ filename, strerror(errno));
+ fclose(fp);
+ return 1;
+ }
+ if (fclose(fp) < 0) {
+ fprintf(stderr, "%s: %s: fclose: %s\n", PNAME,
+ filename, strerror(errno));
+ return 1;
+ }
+
+ /*
+ * Recurse.
+ */
+ if (maxdepth != 0) {
+ unsigned long subindex, subendindex;
+ int newdepth = (maxdepth > 0 ? maxdepth - 1 : maxdepth);
+ char path[1+trie_maxpathlen(t)];
+
+ index++;
+ while (index < endindex) {
+ trie_getpath(t, index, path);
+ get_indices(t, path, &subindex, &subendindex);
+ index = subendindex;
+ if (subendindex - subindex > 1) {
+ if (html_dump(t, subindex, subendindex, newdepth,
+ cfg, pathprefix))
+ return 1;
+ }
+ }
+ }
+ return 0;
+}