Compare commits

...

2 Commits

Author SHA1 Message Date
Xavier Del Campo Romero 2da827c9f8
Rely on make(1) 2023-07-06 02:45:35 +02:00
Xavier Del Campo Romero 9cc9d80008
WIP thumbnail 2023-07-06 02:45:26 +02:00
7 changed files with 366 additions and 20 deletions

View File

@ -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

View File

@ -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

57
main.c
View File

@ -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(int argc, char *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;

132
page.c
View File

@ -45,6 +45,69 @@
" <input type=\"submit\" value=\"Submit\">\n" \
" </form>\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);
}

5
page.h
View File

@ -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 */

135
tngen Executable file
View File

@ -0,0 +1,135 @@
#! /bin/sh
set -e
usage()
{
echo "$0 [-s <size>] [-j <jobs>] [-h] <path>"
}
SIZE=96
while getopts j:s:h arg
do
case $arg in
j) JOBS="$OPTARG"
;;
s) SIZE="$OPTARG"
;;
h) usage
exit 0
;;
?) usage >&2
exit 1
;;
esac
done
shift $(($OPTIND - 1))
if [ $# != 1 ]; then
usage >&2
exit 1
fi
DIR="$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
}
scan()
{
while read d
do
if [ -n "$d" ]
then
scan "$d"
fi
TDIR="$(echo "$1" | sed "s,$DIR/user,$DIR/thumbnails,")"
eval find \"$1\"/* -prune >/dev/null || return 0
mkdir -p "$TDIR"
MK="$(echo "$TDIR/Makefile")"
cat <<-"EOF" > "$MK"
.POSIX:
EOF
echo "DIRS=\\" >> "$MK"
eval find \"$TDIR\"/* -prune -type d > "$TDIR/.dirs"
while read dir
do
D="$(echo "$dir" | sed "s, ,\\\\ ,g")"
echo "$D \\" >> "$MK"
done < "$TDIR/.dirs"
echo >> "$MK"
echo "DEPS=\\" >> "$MK"
eval find \"$1\"/* -prune -type f \
-a -iname '*.jpeg' \
-o -iname '*.jpg' \
-o -iname '*.png' \
-o -iname '*.jpeg' > "$TDIR/.files"
while read f
do
THUMBNAIL="$(echo "$f" | sed "s,$DIR/user/,$DIR/thumbnails/," \
| sed "s, ,\\\\ ,g")"
echo "$THUMBNAIL \\" >> "$MK"
done < "$TDIR/.files"
echo >> "$MK"
echo 'all: $(DEPS) $(DIRS)' >> "$MK"
echo 'FORCE:' >> "$MK"
while read dir
do
D="$(echo "$dir" | sed "s, ,\\\\ ,g")"
echo "$D: FORCE" >> "$MK"
printf '\t+cd $@ && $(MAKE)\n' >> "$MK"
done < "$TDIR/.dirs"
while read f
do
THUMBNAIL="$(echo "$f" | sed "s,$DIR/user/,$DIR/thumbnails/," \
| sed "s, ,\\\\ ,g")"
EF=$(echo "$f" | sed "s, ,\\\\ ,g")
echo $THUMBNAIL: $EF >> "$MK"
printf '\tconvert -thumbnail x%d "$<" "$@" || :\n' $SIZE >> "$MK"
done < "$TDIR/.files"
echo "Successfully generated $MK"
done <<-EOF
$(eval find \"$1\"/* -prune -type d || return 0)
EOF
}
echo Generating Makefiles...
scan "$DIR/user"
cd "$DIR/thumbnails" && make ${JOBS:+-j$JOBS}

51
watchdir Executable file
View File

@ -0,0 +1,51 @@
#! /bin/sh
usage()
{
echo "$0 [-s <size>] [-r] [-h] [-j <jobs>] <dir>"
}
REGEN=0
SIZE=96
while getopts rj:s:h arg
do
case $arg in
j) JOBS="$OPTARG"
;;
r) REGEN=1
;;
s) SIZE="$OPTARG"
;;
h) usage
exit 0
;;
?) 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} \
${JOBS:+-j$JOBS} \
"$DIR"
while :
do
F="$(inotifywait -e modify,move,create,delete \
--format "%w%f" -qr "$DIR/user/")"
"$(dirname $0)/tngen" \
${SIZE:+-s$SIZE} \
${JOBS:+-j$JOBS} \
"$DIR"
done