From 611c8d82b6e128d720bbedb790109807ffe33f5d Mon Sep 17 00:00:00 2001 From: Xavier Del Campo Romero Date: Mon, 19 Jun 2023 01:47:43 +0200 Subject: [PATCH] WIP thumbnail --- Makefile | 2 +- README.md | 4 +- main.c | 57 ++++++++++++++++++++++- page.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++------- page.h | 5 ++- tngen | 76 +++++++++++++++++++++++++++++++ watchdir | 52 +++++++++++++++++++++ 7 files changed, 308 insertions(+), 20 deletions(-) create mode 100755 tngen create mode 100755 watchdir diff --git a/Makefile b/Makefile index 09f3cb2..6ed7cfe 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .POSIX: PROJECT = slcl -O = -Og +O = -O1 CDEFS = -D_FILE_OFFSET_BITS=64 # Required for large file support on 32-bit. CFLAGS = $(O) $(CDEFS) -g -Wall -Idynstr/include -MD -MF $(@:.o=.d) LIBS = -lcjson -lssl -lm -lcrypto diff --git a/README.md b/README.md index 1c62dba..b6ea50d 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ to `slcl`. If required, encryption should be done before uploading e.g.: using - `xxd` (for [`usergen`](usergen) only). - `jq` (for [`usergen`](usergen) only). - CMake (optional). +- `entr` (for [`watchdir`](watchdir) only). +- ImageMagick (for [`tngen`](tngen) only). ### Ubuntu / Debian @@ -57,7 +59,7 @@ sudo apt install build-essential libcjson-dev libssl-dev #### Optional packages ```sh -sudo apt install cmake xxd jq +sudo apt install cmake xxd jq inotify-tools imagemagick ``` ## How to use diff --git a/main.c b/main.c index 2313fec..47a01c1 100644 --- a/main.c +++ b/main.c @@ -642,10 +642,14 @@ static int search(const struct http_payload *const p, int ret = -1; const struct auth *const a = user; const char *const username = p->cookie.field, *const root = auth_dir(a); - struct page_search s = {0}; int (*f)(struct http_response *); char *dir = NULL; struct dynstr userd, d, res; + struct page_search s = + { + .username = username, + .adir = root + }; dynstr_init(&userd); dynstr_init(&d); @@ -917,6 +921,8 @@ static int getnode(const struct http_payload *const p, .r = r, .args = p->args, .n_args = p->n_args, + .username = username, + .adir = adir, .dir = dir.str, .root = root.str, .res = d.str, @@ -1293,6 +1299,54 @@ end: return ret; } +static int getthumbnail(const struct http_payload *const p, + struct http_response *const r, void *const user) +{ + int ret = -1; + struct auth *const a = user; + struct dynstr d, dir; + + dynstr_init(&d); + dynstr_init(&dir); + + if (auth_cookie(a, &p->cookie)) + { + fprintf(stderr, "%s: auth_cookie failed\n", __func__); + ret = page_forbidden(r); + goto end; + } + + const char *const adir = auth_dir(a); + + if (!adir) + { + fprintf(stderr, "%s: auth_dir failed\n", __func__); + goto end; + } + else if (dynstr_append(&d, "%s%s", adir, p->resource)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (dynstr_dup(&dir, &d)) + { + fprintf(stderr, "%s: dynstr_dup failed\n", __func__); + goto end; + } + else if (page_thumbnail(r, d.str)) + { + fprintf(stderr, "%s: page_public failed\n", __func__); + goto end; + } + + ret = 0; + +end: + dynstr_free(&d); + dynstr_free(&dir); + return ret; +} + static void usage(char *const argv[]) { fprintf(stderr, "%s [-t tmpdir] [-p port] dir\n", *argv); @@ -1456,6 +1510,7 @@ int main(const int argc, char *const argv[]) || handler_add(h, "/share", HTTP_OP_POST, share, a) || handler_add(h, "/upload", HTTP_OP_POST, upload, a) || handler_add(h, "/mkdir", HTTP_OP_POST, createdir, a) + || handler_add(h, "/thumbnails/*", HTTP_OP_GET, getthumbnail, a) || handler_listen(h, port)) goto end; diff --git a/page.c b/page.c index 1b0c2eb..473fd59 100644 --- a/page.c +++ b/page.c @@ -45,6 +45,69 @@ " \n" \ " \n" #define MAXSIZEFMT "18446744073709551615.0 XiB" +/* dir 0x555555574460 "/user/" */ +/* res 0x5555555c0060 "/home/xavier/db//user/a/" */ +/* name 0x5555555c05a3 "Abuelitos-2.jpg" */ +static int prepare_thumbnail(struct html_node *const n, + const struct stat *const sb, const struct page_resource *const pr, + const char *const name, const char *const sep) +{ + int ret = -1; + struct html_node *img; + struct dynstr abs, rel; + struct stat tsb; + + dynstr_init(&abs); + dynstr_init(&rel); + + if (!S_ISREG(sb->st_mode)) + { + ret = 0; + goto end; + } + else if (!pr->adir || !pr->username) + { + ret = 0; + goto end; + } + else if (dynstr_append(&rel, "/thumbnails/%s%s%s%s", + pr->username, pr->dir + strlen("/user"), sep, name)) + { + fprintf(stderr, "%s: dynstr_append rel failed\n", __func__); + goto end; + } + else if (dynstr_append(&abs, "%s%s", pr->adir, rel.str)) + { + fprintf(stderr, "%s: dynstr_append rel failed\n", __func__); + goto end; + } + else if (stat(abs.str, &tsb)) + { + if (errno == ENOENT) + ret = 0; + else + fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno)); + + goto end; + } + else if (!(img = html_node_add_child(n, "img"))) + { + fprintf(stderr, "%s: html_node_add_child failed\n", __func__); + goto end; + } + else if (html_node_add_attr(img, "src", rel.str)) + { + fprintf(stderr, "%s: html_node_add_attr src failed\n", __func__); + goto end; + } + + ret = 0; + +end: + dynstr_free(&abs); + dynstr_free(&rel); + return ret; +} static int prepare_name(struct html_node *const n, struct stat *const sb, const char *const dir, const char *const name) @@ -283,18 +346,18 @@ end: return ret; } -static int add_element(struct html_node *const n, const char *const dir, - const char *const res, const char *const name) +static int add_element(struct html_node *const n, + const struct page_resource *const pr, const char *const name) { int ret = -1; - enum {NAME, SIZE, DATE, SHARE, PREVIEW, COLUMNS}; + enum {THUMBNAIL, NAME, SIZE, DATE, SHARE, PREVIEW, COLUMNS}; struct html_node *tr, *td[COLUMNS]; struct dynstr path; - const char *const sep = res[strlen(res) - 1] != '/' ? "/" : ""; + const char *const sep = pr->res[strlen(pr->res) - 1] != '/' ? "/" : ""; dynstr_init(&path); - if (dynstr_append(&path, "%s%s%s", res, sep, name)) + if (dynstr_append(&path, "%s%s%s", pr->res, sep, name)) { fprintf(stderr, "%s: dynstr_append failed\n", __func__); goto end; @@ -321,7 +384,12 @@ static int add_element(struct html_node *const n, const char *const dir, __func__, path.str, strerror(errno)); goto end; } - else if (prepare_name(td[NAME], &sb, dir, name)) + else if (prepare_thumbnail(td[THUMBNAIL], &sb, pr, name, sep)) + { + fprintf(stderr, "%s: prepare_thumbnail failed\n", __func__); + goto end; + } + else if (prepare_name(td[NAME], &sb, pr->dir, name)) { fprintf(stderr, "%s: prepare_name failed\n", __func__); goto end; @@ -336,12 +404,12 @@ static int add_element(struct html_node *const n, const char *const dir, fprintf(stderr, "%s: prepare_date failed\n", __func__); goto end; } - else if (prepare_share(td[SHARE], &sb, dir, name)) + else if (prepare_share(td[SHARE], &sb, pr->dir, name)) { fprintf(stderr, "%s: prepare_date failed\n", __func__); goto end; } - else if (prepare_preview(td[PREVIEW], &sb, dir, name)) + else if (prepare_preview(td[PREVIEW], &sb, pr->dir, name)) { fprintf(stderr, "%s: prepare_date failed\n", __func__); goto end; @@ -983,12 +1051,12 @@ end: return ret; } -static int add_elements(const char *const root, const char *const res, - const char *const dir, struct html_node *const table) +static int add_elements(const struct page_resource *const pr, + struct html_node *const table) { 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) { @@ -1002,9 +1070,9 @@ static int add_elements(const char *const root, const char *const res, 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)) + else if (add_element(table, pr, name)) { fprintf(stderr, "%s: add_element failed\n", __func__); goto end; @@ -1036,7 +1104,7 @@ 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)) + else if (add_elements(pr, table)) { fprintf(stderr, "%s: read_elements failed\n", __func__); goto end; @@ -1649,8 +1717,15 @@ static int add_search_results(struct html_node *const n, for (size_t i = 0; i < s->n; i++) { const struct page_search_result *const r = &s->results[i]; + const struct page_resource pr = + { + .dir = "/user/", + .adir = s->adir, + .username = s->username, + .res = s->root + }; - if (add_element(table, "/user/", s->root, r->name)) + if (add_element(table, &pr, r->name)) { fprintf(stderr, "%s: add_element failed\n", __func__); return -1; @@ -1782,3 +1857,30 @@ end: return ret; } + +int page_thumbnail(struct http_response *const r, const char *const res) +{ + struct stat sb; + + if (stat(res, &sb)) + { + if (errno == ENOENT) + return page_not_found(r); + else + { + fprintf(stderr, "%s: stat(2) %s: %s\n", + __func__, res, strerror(errno)); + return -1; + } + } + + const mode_t m = sb.st_mode; + + if (!S_ISREG(m)) + { + fprintf(stderr, "%s: only regular files are supported\n", __func__); + return 1; + } + + return serve_file(r, &sb, res, true); +} diff --git a/page.h b/page.h index 2554bdc..03420fb 100644 --- a/page.h +++ b/page.h @@ -12,7 +12,7 @@ struct page_quota struct page_resource { struct http_response *r; - const char *dir, *root, *res; + const char *adir, *username, *dir, *root, *res; const struct page_quota *q; const struct http_arg *args; size_t n_args; @@ -25,7 +25,7 @@ struct page_search char *name; } *results; - const char *root; + const char *root, *adir, *username; size_t n; }; @@ -40,5 +40,6 @@ int page_share(struct http_response *r, const char *path); int page_quota_exceeded(struct http_response *r, unsigned long long len, unsigned long long quota); int page_search(struct http_response *r, const struct page_search *s); +int page_thumbnail(struct http_response *r, const char *res); #endif /* PAGE_H */ diff --git a/tngen b/tngen new file mode 100755 index 0000000..6b871de --- /dev/null +++ b/tngen @@ -0,0 +1,76 @@ +#! /bin/sh + +set -e + +usage() +{ + echo "$0 [-s ] [-h] -d " +} + +SIZE=96 + +while getopts s:d:h arg +do + case $arg in + d) DIR="$OPTARG" + ;; + s) SIZE="$OPTARG" + ;; + h) usage + exit 0 + ;; + ?) usage >&2 + exit 1 + ;; + esac +done + +if [ -z "$DIR" ] +then + usage >&2 + exit 1 +fi + +shift $(($OPTIND - 1)) + +if [ $# != 1 ]; then + usage >&2 + exit 1 +fi + +F="$1" + +gen() +{ + IN="$1" + OUT="$2" + + if [ -z "$OUT" ] + then + echo Expected output filename >&2 + return 1 + fi + + mkdir -p "$(dirname "$OUT")" + + if convert -thumbnail x$SIZE "$IN" "$OUT" + then + echo Created $OUT + else + echo Failed to create $OUT >&2 + fi +} + +while read f; do + if [ "$f" != "" ] + then + THUMBNAIL=$(echo "$f" | sed "s,$DIR/user/,$DIR/thumbnails/,") + gen "$f" "$THUMBNAIL" + fi +done <<-EOF + $(find "$F" -type f \ + -a -iname '*.jpeg' \ + -o -iname '*.jpg' \ + -o -iname '*.png' \ + -o -iname '*.jpeg') +EOF diff --git a/watchdir b/watchdir new file mode 100755 index 0000000..fe215b2 --- /dev/null +++ b/watchdir @@ -0,0 +1,52 @@ +#! /bin/sh + +usage() +{ + echo "$0 [-s ] [-r] [-h] [-d ] " +} + +REGEN=0 +SIZE=96 + +while getopts rs:d:h arg +do + case $arg in + r) REGEN=1 + ;; + s) SIZE="$OPTARG" + ;; + h) usage + exit 0 + ;; + d) SUBDIR="$OPTARG" + ;; + ?) usage >&2 + exit 1 + ;; + esac +done + +shift $(($OPTIND - 1)) + +if [ $# != 1 ]; then + usage >&2 + exit 1 +fi + +DIR="$1" + +[ "$REGEN" -eq 1 ] && "$(dirname $0)/tngen" \ + ${SIZE:+-s$SIZE} \ + -d "$DIR" \ + "$DIR/user" + +while : +do + F="$(inotifywait -e modify,move,create,delete \ + --format "%w%f" -qr "$DIR/user/")" + sleep 1 # TODO: revisit this + "$(dirname $0)/tngen" \ + ${SIZE:+-s$SIZE} \ + -d "$DIR" \ + "$F" +done