\dd In this mode, \cw{agedu} generates a textual report on standard
output, listing the disk usage in the specified directory and all
-its subdirectories down to a fixed depth. By default that depth is
+its subdirectories down to a given depth. By default that depth is
1, so that you see a report for \e{directory} itself and all of its
-immediate subdirectories. You can configure a different depth using
-\cw{-d}, described in the next section.
+immediate subdirectories. You can configure a different depth (or no
+depth limit) using \cw{-d}, described in the next section.
\lcont{
\dd In this mode, \cw{agedu} will generate an HTML report of the
disk usage in the specified directory and its immediate
subdirectories, in the same form that it serves from its web server
-in \cw{-w} mode. However, this time, a single HTML report will be
-generated and simply written to standard output, with no hyperlinks
-pointing to other similar pages.
+in \cw{-w} mode.
+
+\lcont{
+
+By default, a single HTML report will be generated and simply
+written to standard output, with no hyperlinks pointing to other
+similar pages. If you also specify the \cw{-d} option (see below),
+\cw{agedu} will instead write out a collection of HTML files with
+hyperlinks between them, and call the top-level file
+\cw{index.html}.
+
+}
\U OPTIONS
files in each directory, instead of just giving a combined report
for everything that's not in a subdirectory.
+The following options affect the stand-alone HTML generation mode
+\cw{-H} and the text report mode \cw{-t}.
+
+\dt \cw{-d} \e{depth} or \cw{--depth} \e{depth}
+
+\dd This option controls the maximum depth to which \cw{agedu}
+recurses when generating a text or HTML report.
+
+\lcont{
+
+In text mode, the default is 1, meaning that the report will include
+the directory given on the command line and all of its immediate
+subdirectories. A depth of two includes another level below that,
+and so on; a depth of zero means \e{only} the directory on the
+command line.
+
+In HTML mode, specifying this option switches \cw{agedu} from
+writing out a single HTML file to writing out multiple files which
+link to each other. A depth of 1 means \cw{agedu} will write out an
+HTML file for the given directory and also one for each of its
+immediate subdirectories.
+
+If you want \cw{agedu} to recurse as deeply as possible, give the
+special word \cq{max} as an argument to \cw{-d}.
+
+}
+
+\dt \cw{-o} \e{filename} or \cw{--output} \e{filename}
+
+\dd This option is used to specify an output file for \cw{agedu} to
+write its report to. In text mode or single-file HTML mode, the
+argument is treated as the name of a file. In multiple-file HTML
+mode, the argument is treated as the name of a directory: the
+directory will be created if it does not already exist, and the
+output HTML files will be created inside it.
+
The following options affect the web server mode \cw{-w}, and in one
case also the stand-alone HTML generation mode \cw{-H}:
}
static void text_query(const void *mappedfile, const char *querydir,
- time_t t, int showfiles, int depth)
+ time_t t, int showfiles, int depth, FILE *fp)
{
size_t maxpathlen;
char *pathbuf;
if (size == 0)
return; /* no space taken up => no display */
- if (depth > 0) {
+ if (depth != 0) {
/*
* Now scan for first-level subdirectories and report
* those too.
*/
+ int newdepth = (depth > 0 ? depth - 1 : depth);
xi1++;
while (xi1 < xi2) {
trie_getpath(mappedfile, xi1, pathbuf);
- text_query(mappedfile, pathbuf, t, showfiles, depth-1);
+ text_query(mappedfile, pathbuf, t, showfiles, newdepth, fp);
make_successor(pathbuf);
xi1 = trie_before(mappedfile, pathbuf);
}
}
/* Display in units of 1Kb */
- printf("%-11llu %s\n", (size) / 1024, querydir);
+ fprintf(fp, "%-11llu %s\n", (size) / 1024, querydir);
}
/*
HELPOPT("[--web,--html,--text] list individual files") \
VAL(AGERANGE) SHORT(r) LONG(age_range) LONG(range) LONG(ages) \
HELPARG("age[-age]") HELPOPT("[--web,--html] set limits of colour coding") \
+ VAL(OUTFILE) SHORT(o) LONG(output) \
+ HELPARG("filename") HELPOPT("[--html] specify output file or directory name") \
VAL(SERVERADDR) LONG(address) LONG(addr) LONG(server_address) \
LONG(server_addr) \
HELPARG("addr[:port]") HELPOPT("[--web] specify HTTP server address") \
HELPARG("filename") HELPOPT("[--web] read HTTP Basic user/pass from file") \
VAL(AUTHFD) LONG(auth_fd) \
HELPARG("fd") HELPOPT("[--web] read HTTP Basic user/pass from fd") \
- VAL(TQDEPTH) SHORT(d) LONG(depth) LONG(max_depth) LONG(maximum_depth) \
- HELPARG("levels") HELPOPT("[--text] recurse to this many levels") \
+ VAL(DEPTH) SHORT(d) LONG(depth) LONG(max_depth) LONG(maximum_depth) \
+ HELPARG("levels") HELPOPT("[--text,--html] recurse to this many levels") \
VAL(MINAGE) SHORT(a) LONG(age) LONG(min_age) LONG(minimum_age) \
HELPARG("age") HELPOPT("[--text] include only files older than this") \
HELPPFX("also") \
const char *httpserveraddr = NULL;
int httpserverport = 0;
const char *httpauthdata = NULL;
+ const char *outfile = NULL;
int auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
int progress = 1;
struct inclusion_exclusion *inex = NULL;
int ninex = 0, inexsize = 0;
int crossfs = 0;
- int tqdepth = 1;
+ int depth = -1, gotdepth = 0;
int fakediratimes = 1;
int mtime = 0;
int showfiles = 0;
case OPT_DATAFILE:
filename = optval;
break;
- case OPT_TQDEPTH:
- tqdepth = atoi(optval);
+ case OPT_DEPTH:
+ if (!strcasecmp(optval, "unlimited") ||
+ !strcasecmp(optval, "infinity") ||
+ !strcasecmp(optval, "infinite") ||
+ !strcasecmp(optval, "inf") ||
+ !strcasecmp(optval, "maximum") ||
+ !strcasecmp(optval, "max"))
+ depth = -1;
+ else
+ depth = atoi(optval);
+ gotdepth = 1;
+ break;
+ case OPT_OUTFILE:
+ outfile = optval;
break;
case OPT_MINAGE:
textcutoff = parse_age(now, optval);
if (pathlen > 0 && querydir[pathlen-1] == pathsep)
querydir[--pathlen] = '\0';
- text_query(mappedfile, querydir, textcutoff, showfiles, tqdepth);
+ if (!gotdepth)
+ depth = 1; /* default for text mode */
+ if (outfile != NULL) {
+ FILE *fp = fopen(outfile, "w");
+ if (!fp) {
+ fprintf(stderr, "%s: %s: open: %s\n", PNAME,
+ outfile, strerror(errno));
+ return 1;
+ }
+ text_query(mappedfile, querydir, textcutoff, showfiles,
+ depth, fp);
+ fclose(fp);
+ } else {
+ text_query(mappedfile, querydir, textcutoff, showfiles,
+ depth, stdout);
+ }
munmap(mappedfile, totalsize);
} else if (mode == HTML) {
} else if (!index_has_root(mappedfile, xi)) {
fprintf(stderr, "%s: pathname '%s' is"
" a file, not a directory\n", PNAME, querydir);
- } else {
+ } else if (!gotdepth) {
+ /*
+ * Single output file.
+ */
cfg.format = NULL;
+ cfg.rootpage = NULL;
cfg.autoage = htmlautoagerange;
cfg.oldest = htmloldest;
cfg.newest = htmlnewest;
cfg.showfiles = showfiles;
- html = html_query(mappedfile, xi, &cfg);
- fputs(html, stdout);
+ html = html_query(mappedfile, xi, &cfg, 0);
+ if (outfile != NULL) {
+ FILE *fp = fopen(outfile, "w");
+ if (!fp) {
+ fprintf(stderr, "%s: %s: open: %s\n", PNAME,
+ outfile, strerror(errno));
+ return 1;
+ } else if (fputs(html, fp) < 0) {
+ fprintf(stderr, "%s: %s: write: %s\n", PNAME,
+ outfile, strerror(errno));
+ fclose(fp);
+ return 1;
+ } else if (fclose(fp) < 0) {
+ fprintf(stderr, "%s: %s: fclose: %s\n", PNAME,
+ outfile, strerror(errno));
+ return 1;
+ }
+ } else {
+ fputs(html, stdout);
+ }
+ } else {
+ /*
+ * Multiple output files.
+ */
+ int dirlen = outfile ? 2+strlen(outfile) : 3;
+ char prefix[dirlen];
+ if (outfile)
+ snprintf(prefix, dirlen, "%s/", outfile);
+ else
+ snprintf(prefix, dirlen, "./");
+
+ unsigned long xi2;
+ make_successor(pathbuf);
+ xi2 = trie_before(mappedfile, pathbuf);
+
+ cfg.format = "%lu.html";
+ cfg.rootpage = "index.html";
+ cfg.autoage = htmlautoagerange;
+ cfg.oldest = htmloldest;
+ cfg.newest = htmlnewest;
+ cfg.showfiles = showfiles;
+ if (html_dump(mappedfile, xi, xi2, depth, &cfg, prefix))
+ return 1;
}
munmap(mappedfile, totalsize);
dcfg.port = httpserverport;
dcfg.basicauthdata = httpauthdata;
pcfg.format = NULL;
+ pcfg.rootpage = NULL;
pcfg.autoage = htmlautoagerange;
pcfg.oldest = htmloldest;
pcfg.newest = htmlnewest;
char *path2;
char *href;
size_t hreflen;
- const char *format;
+ const char *format, *rootpage;
unsigned long long thresholds[MAXCOLOUR];
char *titletexts[MAXCOLOUR+1];
time_t now;
*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)
{
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;
}
}
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;
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);
index2 = trie_before(t, path);
trie_getpath(t, index2, path2);
if (!strcmptrailingpathsep(path, path2) && cfg->format) {
- snprintf(href, hreflen, cfg->format, index2);
+ make_filename(href, hreflen, cfg->format, cfg->rootpage, index2);
if (!*href) /* special case that we understand */
strcpy(href, "./");
htprintf(ctx, "<a href=\"%s\">", href);
vecs = sresize(vecs, vecsize, struct vector *);
}
assert(strlen(path2) > pathlen);
- vecs[nvecs] = make_vector(ctx, path2, (xj2 - xj1 > 1), 0,
+ 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];
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;
+}
const char *format;
/*
+ * If "rootpage" is non-NULL, it overrides "format" to give a
+ * special name (e.g. "index.html") to the top-level page of the
+ * index.
+ */
+ const char *rootpage;
+
+ /*
* Time stamps to assign to the extreme ends of the colour
* scale. If "autoage" is true, they are ignored and the time
* stamps are derived from the limits of the age data stored
* against the pathname at a given index. Returns a dynamically
* allocated piece of memory containing the entire HTML document,
* as an ordinary C zero-terminated string.
+ *
+ * 'downlink' is TRUE if hyperlinks should be generated for
+ * subdirectories. (This can also be disabled by setting cfg->format
+ * to NULL, but that also disables the upward hyperlinks to parent
+ * directories. Setting cfg->format to non-NULL but downlink to NULL
+ * will generate uplinks but no downlinks.)
*/
char *html_query(const void *t, unsigned long index,
- const struct html_config *cfg);
+ const struct html_config *cfg, int downlink);
+
+/*
+ * Recursively output a dump of lots of HTML files which crosslink
+ * to each other. cfg->format and cfg->rootpath will be used to
+ * generate the filenames for both the hyperlinks and the output
+ * file names; the file names will have "pathprefix" prepended to
+ * them before being opened.
+ *
+ * "index" and "endindex" point to the region of index file that
+ * should be generated by the dump, which must be a subdirectory.
+ *
+ * "maxdepth" limits the depth of recursion. Setting it to zero
+ * outputs only one page, 1 outputs the current directory and its
+ * immediate children but no further, and so on. Making it negative
+ * gives unlimited depth.
+ *
+ * Return value is 0 on success, or 1 if an error occurs during
+ * output.
+ */
+int html_dump(const void *t, unsigned long index, unsigned long endindex,
+ int maxdepth, const struct html_config *cfg,
+ const char *pathprefix);
ret = http_error("404", "Not Found", NULL,
"This is not a valid pathname index.");
} else {
- document = html_query(ctx->t, index, cfg);
+ document = html_query(ctx->t, index, cfg, 1);
if (document) {
ret = http_success("text/html", 1, document);
sfree(document);