diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2024-08-28 03:10:06 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2024-08-28 03:57:27 +0200 |
| commit | 6b7acc7eaf401b2d4fdf615543f4364fc77b9255 (patch) | |
| tree | 925d9fe21989290b6ab8b5fc6ab16d672bda44d9 | |
| parent | 49d8e4397a80ab373737b8baf4588bef5bfa717f (diff) | |
| download | slcl-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.c | 320 | ||||
| -rw-r--r-- | style.c | 8 |
2 files changed, 304 insertions, 24 deletions
@@ -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)) @@ -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; |
