Fiddle about with the configure script so it notices the need for
[sgt/agedu] / html.c
diff --git a/html.c b/html.c
index f7f8c3e..e72d1bd 100644 (file)
--- a/html.c
+++ b/html.c
@@ -2,18 +2,9 @@
  * 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"
 
@@ -27,18 +18,26 @@ struct html {
     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) {
@@ -51,7 +50,7 @@ static void vhtprintf(struct html *ctx, char *fmt, va_list ap)
     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);
@@ -173,14 +172,25 @@ static void get_indices(const void *t, char *path,
        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)
@@ -215,7 +225,6 @@ static void begin_colour_bar(struct html *ctx)
 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;
@@ -224,26 +233,12 @@ static void add_to_colour_bar(struct html *ctx, int colour, int pixels)
     else                              /* background grey */
        r = g = b = 240;
 
-    if (colour < 0) {
-       /* no title text here */
-    } else if (colour == 0) {
-       strcpy(buf, "&lt; ");
-       round_and_format_age(ctx, ctx->thresholds[0], buf+5, 0);
-    } else if (colour == MAXCOLOUR) {
-       strcpy(buf, "&gt; ");
-       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");
     }
 }
@@ -254,8 +249,9 @@ static void end_colour_bar(struct html *ctx)
 }
 
 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];
 };
@@ -279,18 +275,25 @@ int vec_compare(const void *av, const void *bv)
        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);
 
@@ -302,7 +305,7 @@ static struct vector *make_vector(struct html *ctx, char *path,
            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;
@@ -314,12 +317,41 @@ static void print_heading(struct html *ctx, const char *title)
             "<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;
+
+    while (size >= 1024 && shift < lenof(fmts)-1) {
+       size >>= 10;
+       shift++;
+    }
+    *display_size = (double)size;
+    *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
@@ -330,7 +362,7 @@ static void write_report_line(struct html *ctx, struct vector *vec)
      * 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) {
@@ -341,9 +373,11 @@ static void write_report_line(struct html *ctx, struct vector *vec)
      * 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.
@@ -375,19 +409,37 @@ static void write_report_line(struct html *ctx, struct vector *vec)
        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;
@@ -406,6 +458,7 @@ char *html_query(const void *t, unsigned long index,
     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);
@@ -425,7 +478,7 @@ char *html_query(const void *t, unsigned long index,
      */
     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");
@@ -461,8 +514,8 @@ char *html_query(const void *t, unsigned long index,
        *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);
@@ -494,13 +547,30 @@ char *html_query(const void *t, unsigned long index,
        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, "&lt; ");
+           round_and_format_age(ctx, ctx->thresholds[0], buf+5, 0);
+       } else if (i == MAXCOLOUR) {
+           strcpy(buf, "&gt; ");
+           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++)
@@ -517,7 +587,8 @@ char *html_query(const void *t, unsigned long index,
      * 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.
@@ -525,7 +596,7 @@ char *html_query(const void *t, unsigned long index,
     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]);
 
@@ -546,14 +617,15 @@ char *html_query(const void *t, unsigned long index,
        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 + subdirpos);
+       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++;
@@ -585,3 +657,68 @@ char *html_query(const void *t, unsigned long index,
 
     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;
+}