aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2024-08-28 03:10:06 +0200
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2024-08-28 03:57:27 +0200
commit6b7acc7eaf401b2d4fdf615543f4364fc77b9255 (patch)
tree925d9fe21989290b6ab8b5fc6ab16d672bda44d9
parent49d8e4397a80ab373737b8baf4588bef5bfa717f (diff)
downloadslcl-6b7acc7eaf401b2d4fdf615543f4364fc77b9255.tar.gz
Implement directory paging
This allows directories with many files and directories inside them to be split into pages, in order to limit resource usage.
-rw-r--r--page.c320
-rw-r--r--style.c8
2 files changed, 304 insertions, 24 deletions
diff --git a/page.c b/page.c
index 6c58ab1..0d15473 100644
--- a/page.c
+++ b/page.c
@@ -50,7 +50,7 @@
struct element_stats
{
- unsigned long long n_dirs, n_files;
+ unsigned long long n_dirs, n_files, cur_page, n_pages;
};
static int prepare_rm_checkbox(struct html_node *const n,
@@ -400,15 +400,7 @@ static int add_element(struct html_node *const n, const char *const dir,
__func__, path.str, strerror(errno));
goto end;
}
- else if (st && !parentdir)
- {
- if (S_ISDIR(sb.st_mode))
- st->n_dirs++;
- else if (S_ISREG(sb.st_mode))
- st->n_files++;
- }
-
- if (chbx && !parentdir
+ else if (chbx && !parentdir
&& prepare_rm_checkbox(td[RM_CHECKBOX], &sb, name))
{
fprintf(stderr, "%s: prepare_rm_checkbox failed\n", __func__);
@@ -1151,13 +1143,158 @@ end:
return ret;
}
-static int add_elements(const char *const root, const char *const res,
- const char *const dir, struct html_node *const table,
- struct element_stats *const st)
+static int get_page(const struct page_resource *const pr, size_t *const page)
+{
+ *page = 0;
+
+ for (size_t i = 0; i < pr->n_args; i++)
+ {
+ const struct http_arg *const a = &pr->args[i];
+
+ if (!strcmp(a->key, "page"))
+ {
+ errno = 0;
+
+ char *end;
+ const unsigned long long value = strtoull(a->value, &end, 10);
+
+ if (errno)
+ {
+ fprintf(stderr, "%s: strtoull(3): %s\n",
+ __func__, strerror(errno));
+ return 1;
+ }
+ else if (*end)
+ {
+ fprintf(stderr, "%s: invalid value for page argument: %s\n",
+ __func__, a->value);
+ return 1;
+ }
+ else if (value > SIZE_MAX)
+ {
+ fprintf(stderr, "%s: page value (%llu) exceeds maximum value "
+ "(%ju)\n", __func__, value, (uintmax_t)SIZE_MAX);
+ return 1;
+ }
+
+ *page = value;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int get_file_dir(const struct page_resource *const pr,
+ const char *const name, struct element_stats *const st)
+{
+ int ret = -1;
+ const char *const sep = pr->res[strlen(pr->res) - 1] != '/' ? "/" : "";
+ struct dynstr path;
+ struct stat sb;
+
+ dynstr_init(&path);
+
+ if (!strcmp(name, ".."))
+ {
+ ret = 0;
+ goto end;
+ }
+ else if (dynstr_append(&path, "%s%s%s", pr->res, sep, name))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (stat(path.str, &sb))
+ {
+ fprintf(stderr, "%s: stat(2) %s: %s\n", __func__, path.str,
+ strerror(errno));
+ goto end;
+ }
+ else if (S_ISDIR(sb.st_mode))
+ st->n_dirs++;
+ else if (S_ISREG(sb.st_mode))
+ st->n_files++;
+
+ ret = 0;
+
+end:
+ dynstr_free(&path);
+ return ret;
+}
+
+static int get_files_dirs(const struct page_resource *const pr,
+ struct element_stats *const st, const struct dirent *const *const pde,
+ const size_t n)
+{
+ for (size_t i = 0; i < n; i++)
+ {
+ const struct dirent *const de = pde[i];
+ const char *const name = de->d_name;
+
+ if (!strcmp(name, ".")
+ || (!strcmp(name, "..") && !strcmp(pr->root, pr->res)))
+ continue;
+ else if (get_file_dir(pr, name, st))
+ {
+ fprintf(stderr, "%s: get_file_dir failed\n", __func__);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int get_page_stats(const struct page_resource *const pr,
+ struct element_stats *const st, const struct dirent *const *const pde,
+ const size_t n, size_t *const start, size_t *const end)
+{
+ /* TODO: allow user to define limit. */
+ static const size_t limit = 100;
+ size_t page;
+ const int ret = get_page(pr, &page);
+
+ if (ret)
+ {
+ fprintf(stderr, "%s: get_page failed\n", __func__);
+ return ret;
+ }
+
+ const size_t n_pages = n / limit;
+
+ if (page > n_pages)
+ {
+ fprintf(stderr, "%s: invalid page %zu (max %zu)\n", __func__, page,
+ n_pages);
+ return 1;
+ }
+ else if (page > (size_t)SIZE_MAX / limit)
+ {
+ fprintf(stderr, "%s: page overflow detected: %zu, limit: %zu\n",
+ __func__, page, limit);
+ return 1;
+ }
+ else if (get_files_dirs(pr, st, pde, n))
+ {
+ fprintf(stderr, "%s: get_files_dirs failed\n", __func__);
+ return -1;
+ }
+
+ const size_t tmpend = limit * (page + 1);
+
+ *start = limit * page;
+ *end = tmpend > n ? n : tmpend;
+ st->cur_page = page;
+ st->n_pages = n_pages;
+ return 0;
+}
+
+static int add_elements(const struct page_resource *const pr,
+ struct html_node *const table, struct element_stats *const st)
{
int ret = -1;
struct dirent **pde = NULL;
- const int n = scandir(res, &pde, NULL, alphasort);
+ const int n = scandir(pr->res, &pde, NULL, alphasort);
if (n < 0)
{
@@ -1167,15 +1304,24 @@ static int add_elements(const char *const root, const char *const res,
*st = (const struct element_stats){0};
- for (int i = 0; i < n; i++)
+ size_t start, end;
+ const struct dirent *const *const cpde = (const struct dirent *const *)pde;
+
+ if ((ret = get_page_stats(pr, st, cpde, n, &start, &end)))
+ {
+ fprintf(stderr, "%s: get_page_stats failed\n", __func__);
+ goto end;
+ }
+
+ for (size_t i = start; i < end; i++)
{
const struct dirent *const de = pde[i];
const char *const name = de->d_name;
if (!strcmp(name, ".")
- || (!strcmp(name, "..") && !strcmp(root, res)))
+ || (!strcmp(name, "..") && !strcmp(pr->root, pr->res)))
continue;
- else if (add_element(table, dir, res, name, true, st))
+ else if (add_element(table, pr->dir, pr->res, name, true, st))
{
fprintf(stderr, "%s: add_element failed\n", __func__);
goto end;
@@ -1193,8 +1339,121 @@ end:
return ret;
}
-static int prepare_element_stats(struct html_node *const n,
- const struct element_stats *const st)
+static int add_page(const struct page_resource *const pr,
+ const size_t cur_page, struct html_node *const n, const size_t page)
+{
+ int ret = -1;
+ struct html_node *child;
+ struct dynstr d;
+
+ dynstr_init(&d);
+
+ if (page != cur_page)
+ {
+ if (!(child = html_node_add_child(n, "a")))
+ {
+ fprintf(stderr, "%s: html_node_add_child a failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&d, "%s?page=%zu", pr->dir, page))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_add_attr(child, "href", d.str))
+ {
+ fprintf(stderr, "%s: html_node_add_attr failed\n", __func__);
+ goto end;
+ }
+ }
+ else if (!(child = html_node_add_child(n, "p")))
+ {
+ fprintf(stderr, "%s: html_node_add_child p failed\n", __func__);
+ goto end;
+ }
+
+ char buf[sizeof "18446744073709551615"];
+ const int res = snprintf(buf, sizeof buf, "%zu", page);
+
+ if (res < 0 || res >= sizeof buf)
+ {
+ fprintf(stderr, "%s: snprintf(3) failed with %d\n", __func__, res);
+ goto end;
+ }
+ else if (html_node_add_attr(child, "class", "page"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr failed\n", __func__);
+ goto end;
+ }
+ else if (html_node_set_value(child, buf))
+ {
+ fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
+ goto end;
+ }
+
+ ret = 0;
+
+end:
+ dynstr_free(&d);
+ return ret;
+}
+
+static int add_pages(const struct page_resource *const pr,
+ struct html_node *const n, const struct element_stats *const st)
+{
+ struct html_node *const div = html_node_add_child(n, "div");
+ const size_t cur = st->cur_page;
+
+ if (!div)
+ {
+ fprintf(stderr, "%s: html_node_add_child div failed\n", __func__);
+ return -1;
+ }
+ else if (html_node_add_attr(div, "class", "pages"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr failed\n", __func__);
+ return -1;
+ }
+ else if (cur && add_page(pr, cur, div, 0))
+ {
+ fprintf(stderr, "%s: add_page first failed\n", __func__);
+ return -1;
+ }
+
+ static const size_t limit = 10;
+ const size_t min = cur > limit ? cur - limit : 1;
+
+ for (size_t i = min; i < cur; i++)
+ if (add_page(pr, cur, div, i))
+ {
+ fprintf(stderr, "%s: add_page %zu failed\n", __func__, i);
+ return -1;
+ }
+
+ if (add_page(pr, cur, div, cur))
+ {
+ fprintf(stderr, "%s: add_page cur failed\n", __func__);
+ return -1;
+ }
+
+ for (size_t i = cur + 1, j = 0; i < st->n_pages && j < limit; i++, j++)
+ if (add_page(pr, cur, div, i))
+ {
+ fprintf(stderr, "%s: add_page %zu failed\n", __func__, i);
+ return -1;
+ }
+
+ if (cur < st->n_pages && add_page(pr, cur, div, st->n_pages))
+ {
+ fprintf(stderr, "%s: add_page last failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int prepare_element_stats(const struct page_resource *const pr,
+ struct html_node *const n, const struct element_stats *const st)
{
int ret = -1;
struct html_node *const p = html_node_add_child(n, "div");
@@ -1218,6 +1477,11 @@ static int prepare_element_stats(struct html_node *const n,
fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
goto end;
}
+ else if (st->n_pages && add_pages(pr, n, st))
+ {
+ fprintf(stderr, "%s: add_pages failed\n", __func__);
+ goto end;
+ }
ret = 0;
@@ -1242,12 +1506,12 @@ static int list_dir(const struct page_resource *const pr)
fprintf(stderr, "%s: resource_layout failed\n", __func__);
goto end;
}
- else if (add_elements(pr->root, pr->res, pr->dir, table, &st))
+ else if ((ret = add_elements(pr, table, &st)))
{
fprintf(stderr, "%s: read_elements failed\n", __func__);
goto end;
}
- else if (prepare_element_stats(body, &st))
+ else if (prepare_element_stats(pr, body, &st))
{
fprintf(stderr, "%s: prepare_element_stats failed\n", __func__);
goto end;
@@ -1432,10 +1696,18 @@ int page_resource(const struct page_resource *const pr)
if (S_ISDIR(m))
{
- if (list_dir(pr))
+ if ((ret = list_dir(pr)))
{
- fprintf(stderr, "%s: list_dir failed\n", __func__);
- goto end;
+ if (ret < 0)
+ {
+ fprintf(stderr, "%s: list_dir failed\n", __func__);
+ goto end;
+ }
+ else if ((ret = page_bad_request(pr->r)))
+ {
+ fprintf(stderr, "%s: page_bad_request failed\n", __func__);
+ goto end;
+ }
}
}
else if (S_ISREG(m))
diff --git a/style.c b/style.c
index 07feb44..f10d296 100644
--- a/style.c
+++ b/style.c
@@ -50,6 +50,14 @@ const char style_default[] =
"tr:nth-child(even)\n"
"{\n"
" background-color: lightgray;\n"
+ "}\n"
+ ".page\n"
+ "{\n"
+ " margin: 0.2rem;\n"
+ "}\n"
+ ".pages\n"
+ "{\n"
+ " display: flex;\n"
"}\n";
const size_t style_default_len = sizeof style_default - 1;