From e4a253fd2579719add519efa83c2aa28e3cb9a92 Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Mon, 13 Sep 2021 12:58:28 -0400 Subject: client+server: fixed style errors --- server/szurubooru/func/image_hash.py | 5 +++-- server/szurubooru/func/images.py | 3 ++- server/szurubooru/func/mime.py | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/szurubooru/func/image_hash.py b/server/szurubooru/func/image_hash.py index a445e62..05b27a4 100644 --- a/server/szurubooru/func/image_hash.py +++ b/server/szurubooru/func/image_hash.py @@ -5,14 +5,15 @@ from io import BytesIO from typing import Any, Callable, List, Optional, Set, Tuple import numpy as np -from PIL import Image import pillow_avif import pyheif +from PIL import Image from pyheif_pillow_opener import register_heif_opener -register_heif_opener() from szurubooru import config, errors +register_heif_opener() + logger = logging.getLogger(__name__) # Math based on paper from H. Chi Wong, Marshall Bern and David Goldberg diff --git a/server/szurubooru/func/images.py b/server/szurubooru/func/images.py index 101bba8..de41222 100644 --- a/server/szurubooru/func/images.py +++ b/server/szurubooru/func/images.py @@ -6,6 +6,7 @@ import shlex import subprocess from io import BytesIO from typing import List + from PIL import Image as PILImage from szurubooru import errors @@ -17,7 +18,7 @@ logger = logging.getLogger(__name__) def convert_heif_to_png(content: bytes) -> bytes: img = PILImage.open(BytesIO(content)) img_byte_arr = BytesIO() - img.save(img_byte_arr, format='PNG') + img.save(img_byte_arr, format="PNG") return img_byte_arr.getvalue() diff --git a/server/szurubooru/func/mime.py b/server/szurubooru/func/mime.py index 93c096b..3be43f7 100644 --- a/server/szurubooru/func/mime.py +++ b/server/szurubooru/func/mime.py @@ -88,6 +88,7 @@ def is_animated_gif(content: bytes) -> bool: and len(re.findall(pattern, content)) > 1 ) + def is_heif(mime_type: str) -> bool: return mime_type.lower() in ( "image/heif", -- cgit v1.3 From 4f57f49ebe5395a4c3a8e20c84c5d881c9f98dfc Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Wed, 15 Sep 2021 16:06:48 -0400 Subject: client+server: migrate to GitHub actions --- .github/workflows/build-containers.yml | 93 ++++++++++++++++++++++++++++++++++ .github/workflows/run-unit-tests.yml | 16 ++++++ client/hooks/build | 16 ------ client/hooks/post_push | 19 ------- server/hooks/build | 7 --- server/hooks/post_push | 19 ------- server/hooks/test | 8 --- 7 files changed, 109 insertions(+), 69 deletions(-) create mode 100644 .github/workflows/build-containers.yml create mode 100644 .github/workflows/run-unit-tests.yml delete mode 100755 client/hooks/build delete mode 100755 client/hooks/post_push delete mode 100755 server/hooks/build delete mode 100755 server/hooks/post_push delete mode 100755 server/hooks/test (limited to 'server') diff --git a/.github/workflows/build-containers.yml b/.github/workflows/build-containers.yml new file mode 100644 index 0000000..41baea7 --- /dev/null +++ b/.github/workflows/build-containers.yml @@ -0,0 +1,93 @@ +name: build-containers +on: [push] +jobs: + build-client: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Determine metadata + run: | + CLOSEST_VER="$(git describe --tags --abbrev=0 $GITHUB_SHA)" + CLOSEST_MAJOR_VER="$(echo ${CLOSEST_VER} | cut -d'.' -f1)" + CLOSEST_MINOR_VER="$(echo ${CLOSEST_VER} | cut -d'.' -f2)" + SHORT_COMMIT=$(echo $GITHUB_SHA | cut -c1-8) + BUILD_INFO="v${CLOSEST_VER}-${SHORT_COMMIT}" + BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + + echo "major_tag=${CLOSEST_MAJOR_VER}" >> $GITHUB_ENV + echo "minor_tag=${CLOSEST_MAJOR_VER}.${CLOSEST_MINOR_VER}" >> $GITHUB_ENV + echo "build_info=${BUILD_INFO}" >> $GITHUB_ENV + echo "build_date=${BUILD_DATE}" >> $GITHUB_ENV + + echo "Build Info: ${BUILD_INFO}" + echo "Build Date: ${BUILD_DATE}" + + - name: Build container + run: > + docker build + --build-arg BUILD_INFO=${{ env.build_info }} + --build-arg BUILD_DATE=${{ env.build_date }} + --build-arg SOURCE_COMMIT=$GITHUB_SHA + --build-arg DOCKER_REPO=szurubooru/client + -t "szurubooru/client:latest" + -t "szurubooru/client:${{ env.major_tag }}" + -t "szurubooru/client:${{ env.minor_tag }}" + ./client + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Push containers + run: docker push -a szurubooru/client + + build-server: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Determine metadata + run: | + CLOSEST_VER="$(git describe --tags --abbrev=0 $GITHUB_SHA)" + CLOSEST_MAJOR_VER="$(echo ${CLOSEST_VER} | cut -d'.' -f1)" + CLOSEST_MINOR_VER="$(echo ${CLOSEST_VER} | cut -d'.' -f2)" + SHORT_COMMIT=$(echo $GITHUB_SHA | cut -c1-8) + BUILD_INFO="v${CLOSEST_VER}-${SHORT_COMMIT}" + BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + + echo "major_tag=${CLOSEST_MAJOR_VER}" >> $GITHUB_ENV + echo "minor_tag=${CLOSEST_MAJOR_VER}.${CLOSEST_MINOR_VER}" >> $GITHUB_ENV + echo "build_info=${BUILD_INFO}" >> $GITHUB_ENV + echo "build_date=${BUILD_DATE}" >> $GITHUB_ENV + + echo "Build Info: ${BUILD_INFO}" + echo "Build Date: ${BUILD_DATE}" + + - name: Build container + run: > + docker build + --build-arg BUILD_DATE=${{ env.build_date }} + --build-arg SOURCE_COMMIT=$GITHUB_SHA + --build-arg DOCKER_REPO=szurubooru/server + -t "szurubooru/server:latest" + -t "szurubooru/server:${{ env.major_tag }}" + -t "szurubooru/server:${{ env.minor_tag }}" + ./server + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Push containers + run: docker push -a szurubooru/server diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml new file mode 100644 index 0000000..87ab4e8 --- /dev/null +++ b/.github/workflows/run-unit-tests.yml @@ -0,0 +1,16 @@ +name: run-unit-tests +on: [pull_request] +jobs: + test-server: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Build test container + run: | + TAG=$(docker build --target testing -q ./server) + echo "image_tag=${TAG}" >> $GITHUB_ENV + + - name: Run unit tests + run: docker run --rm -t ${{ env.image_tag }} --color=no szurubooru/ diff --git a/client/hooks/build b/client/hooks/build deleted file mode 100755 index 46443f4..0000000 --- a/client/hooks/build +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -CLOSEST_VER=$(git describe --tags --abbrev=0 ${SOURCE_COMMIT}) -if git describe --exact-match --abbrev=0 ${SOURCE_COMMIT} 2> /dev/null; then - BUILD_INFO="v${CLOSEST_VER}" -else - BUILD_INFO="v${CLOSEST_VER}-edge-$(git rev-parse --short ${SOURCE_COMMIT})" -fi - -echo "Using BUILD_INFO=${BUILD_INFO}" -docker build \ - --build-arg BUILD_INFO=${BUILD_INFO} \ - --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ - --build-arg SOURCE_COMMIT \ - --build-arg DOCKER_REPO \ - -f $DOCKERFILE_PATH -t $IMAGE_NAME . diff --git a/client/hooks/post_push b/client/hooks/post_push deleted file mode 100755 index 1b1e0ad..0000000 --- a/client/hooks/post_push +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -add_tag() { - echo "Also tagging image as ${DOCKER_REPO}:${1}" - docker tag $IMAGE_NAME $DOCKER_REPO:$1 - docker push $DOCKER_REPO:$1 -} - -CLOSEST_VER=$(git describe --tags --abbrev=0) -CLOSEST_MAJOR_VER=$(echo ${CLOSEST_VER} | cut -d'.' -f1) -CLOSEST_MINOR_VER=$(echo ${CLOSEST_VER} | cut -d'.' -f2) - -add_tag "${CLOSEST_MAJOR_VER}-edge" -add_tag "${CLOSEST_MAJOR_VER}.${CLOSEST_MINOR_VER}-edge" - -if git describe --exact-match --abbrev=0 2> /dev/null; then - add_tag "${CLOSEST_MAJOR_VER}" - add_tag "${CLOSEST_MAJOR_VER}.${CLOSEST_MINOR_VER}" -fi diff --git a/server/hooks/build b/server/hooks/build deleted file mode 100755 index b5e914b..0000000 --- a/server/hooks/build +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -docker build \ - --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ - --build-arg SOURCE_COMMIT \ - --build-arg DOCKER_REPO \ - -f $DOCKERFILE_PATH -t $IMAGE_NAME . diff --git a/server/hooks/post_push b/server/hooks/post_push deleted file mode 100755 index 1b1e0ad..0000000 --- a/server/hooks/post_push +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -add_tag() { - echo "Also tagging image as ${DOCKER_REPO}:${1}" - docker tag $IMAGE_NAME $DOCKER_REPO:$1 - docker push $DOCKER_REPO:$1 -} - -CLOSEST_VER=$(git describe --tags --abbrev=0) -CLOSEST_MAJOR_VER=$(echo ${CLOSEST_VER} | cut -d'.' -f1) -CLOSEST_MINOR_VER=$(echo ${CLOSEST_VER} | cut -d'.' -f2) - -add_tag "${CLOSEST_MAJOR_VER}-edge" -add_tag "${CLOSEST_MAJOR_VER}.${CLOSEST_MINOR_VER}-edge" - -if git describe --exact-match --abbrev=0 2> /dev/null; then - add_tag "${CLOSEST_MAJOR_VER}" - add_tag "${CLOSEST_MAJOR_VER}.${CLOSEST_MINOR_VER}" -fi diff --git a/server/hooks/test b/server/hooks/test deleted file mode 100755 index b325186..0000000 --- a/server/hooks/test +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -set -e - -docker run --rm \ - -t $(docker build --target testing -q .) \ - --color=no szurubooru/ - -exit $? -- cgit v1.3 From c3b81371d83c3daf31aeeb821d4865e232cd6b0d Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Sun, 19 Sep 2021 12:03:32 -0400 Subject: client+server/docker: fix ARM build platform issue --- .github/workflows/build-containers.yml | 4 ++-- client/Dockerfile | 4 ++-- server/Dockerfile | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'server') diff --git a/.github/workflows/build-containers.yml b/.github/workflows/build-containers.yml index f808f65..518bf5b 100644 --- a/.github/workflows/build-containers.yml +++ b/.github/workflows/build-containers.yml @@ -42,7 +42,7 @@ jobs: - name: Build container run: > docker buildx build --push - --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 + --platform linux/amd64,linux/arm/v7,linux/arm64/v8 --build-arg BUILD_INFO=${{ env.build_info }} --build-arg BUILD_DATE=${{ env.build_date }} --build-arg SOURCE_COMMIT=$GITHUB_SHA @@ -93,7 +93,7 @@ jobs: - name: Build container run: > docker buildx build --push - --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 + --platform linux/amd64,linux/arm/v7,linux/arm64/v8 --build-arg BUILD_DATE=${{ env.build_date }} --build-arg SOURCE_COMMIT=$GITHUB_SHA --build-arg DOCKER_REPO=szurubooru/server diff --git a/client/Dockerfile b/client/Dockerfile index 2aeaf3b..3ab0016 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -1,4 +1,4 @@ -FROM node:lts as builder +FROM --platform=$BUILDPLATFORM node:lts as builder WORKDIR /opt/app COPY package.json package-lock.json ./ @@ -12,7 +12,7 @@ ARG CLIENT_BUILD_ARGS="" RUN BASE_URL="__BASEURL__" node build.js --gzip ${CLIENT_BUILD_ARGS} -FROM scratch as approot +FROM --platform=$BUILDPLATFORM scratch as approot COPY docker-start.sh / diff --git a/server/Dockerfile b/server/Dockerfile index 4beec1c..205c8e4 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -37,7 +37,7 @@ COPY ./ /opt/app/ RUN rm -rf /opt/app/szurubooru/tests -FROM prereqs as testing +FROM --platform=$BUILDPLATFORM prereqs as testing WORKDIR /opt/app RUN apk --no-cache add \ -- cgit v1.3 From ad9d3599bccd825fa0f17e0b4334dfcf01e877ef Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Wed, 22 Sep 2021 22:08:07 -0400 Subject: server/net: return more useful error messages --- server/szurubooru/func/net.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/szurubooru/func/net.py b/server/szurubooru/func/net.py index 9dff3c4..3f085a0 100644 --- a/server/szurubooru/func/net.py +++ b/server/szurubooru/func/net.py @@ -42,10 +42,17 @@ def download(url: str, use_video_downloader: bool = False) -> bytes: while (chunk := handle.read(_dl_chunk_size)) : length_tally += len(chunk) if length_tally > config.config["max_dl_filesize"]: - raise DownloadTooLargeError(url) + raise DownloadTooLargeError( + "Download target exceeds maximum. (%d)" + % (config.config["max_dl_filesize"]), + extra_fields={"URL": url}, + ) content_buffer += chunk except urllib.error.HTTPError as ex: - raise DownloadError(url) from ex + raise DownloadError( + "Download target returned HTTP %d. (%s)" % (ex.code, ex.reason), + extra_fields={"URL": url}, + ) from ex if ( youtube_dl_error @@ -69,7 +76,8 @@ def _get_youtube_dl_content_url(url: str) -> str: ) except subprocess.CalledProcessError: raise errors.ThirdPartyError( - "Could not extract content location from %s" % (url) + "Could not extract content location from URL.", + extra_fields={"URL": url}, ) from None -- cgit v1.3 From d08308440713063502979a40c04c641ddef7dde7 Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Thu, 23 Sep 2021 12:24:56 -0400 Subject: server/tests: use transactional db for faster unit tests * `test_modify_saves_non_empty_diffs` needs non-transactional db, so moved to seperate file * Replaced incompatable usage of `db.session.rollback()` with parametrerized function calls * xfail conditionals for search removed, as we can no longer get current driver with binds * Also remove usage of deprecated `pytest.yield_fixture` --- server/szurubooru/tests/api/test_tag_updating.py | 14 ++--- server/szurubooru/tests/conftest.py | 16 +++++- server/szurubooru/tests/func/test_snapshots.py | 42 +-------------- .../func/test_snapshots_transactional_isolation.py | 59 ++++++++++++++++++++++ .../szurubooru/tests/func/test_tag_categories.py | 15 +++--- server/szurubooru/tests/func/test_tags.py | 16 +++--- .../search/configs/test_pool_search_config.py | 2 - .../tests/search/configs/test_tag_search_config.py | 2 - 8 files changed, 91 insertions(+), 75 deletions(-) create mode 100644 server/szurubooru/tests/func/test_snapshots_transactional_isolation.py (limited to 'server') diff --git a/server/szurubooru/tests/api/test_tag_updating.py b/server/szurubooru/tests/api/test_tag_updating.py index 729734d..be5f485 100644 --- a/server/szurubooru/tests/api/test_tag_updating.py +++ b/server/szurubooru/tests/api/test_tag_updating.py @@ -145,8 +145,9 @@ def test_trying_to_update_without_privileges( ) +@pytest.mark.parametrize("type", ["suggestions", "implications"]) def test_trying_to_create_tags_without_privileges( - config_injector, context_factory, tag_factory, user_factory + config_injector, context_factory, tag_factory, user_factory, type ): tag = tag_factory(names=["tag"]) db.session.add(tag) @@ -165,16 +166,7 @@ def test_trying_to_create_tags_without_privileges( with pytest.raises(errors.AuthError): api.tag_api.update_tag( context_factory( - params={"suggestions": ["tag1", "tag2"], "version": 1}, - user=user_factory(rank=model.User.RANK_REGULAR), - ), - {"tag_name": "tag"}, - ) - db.session.rollback() - with pytest.raises(errors.AuthError): - api.tag_api.update_tag( - context_factory( - params={"implications": ["tag1", "tag2"], "version": 1}, + params={type: ["tag1", "tag2"], "version": 1}, user=user_factory(rank=model.User.RANK_REGULAR), ), {"tag_name": "tag"}, diff --git a/server/szurubooru/tests/conftest.py b/server/szurubooru/tests/conftest.py index e7811fe..280987c 100644 --- a/server/szurubooru/tests/conftest.py +++ b/server/szurubooru/tests/conftest.py @@ -43,14 +43,26 @@ def query_logger(pytestconfig): logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) -@pytest.yield_fixture(scope="function", autouse=True) -def session(query_logger, postgresql_db): +@pytest.fixture(scope="function", autouse=True) +def session(query_logger, transacted_postgresql_db): + db.session = transacted_postgresql_db.session + transacted_postgresql_db.create_table(*model.Base.metadata.sorted_tables) + try: + yield transacted_postgresql_db.session + finally: + transacted_postgresql_db.reset_db() + + +@pytest.fixture(scope="function") +def nontransacted_session(query_logger, postgresql_db): + old_db_session = db.session db.session = postgresql_db.session postgresql_db.create_table(*model.Base.metadata.sorted_tables) try: yield postgresql_db.session finally: postgresql_db.reset_db() + db.session = old_db_session @pytest.fixture diff --git a/server/szurubooru/tests/func/test_snapshots.py b/server/szurubooru/tests/func/test_snapshots.py index da93530..dc68ff0 100644 --- a/server/szurubooru/tests/func/test_snapshots.py +++ b/server/szurubooru/tests/func/test_snapshots.py @@ -1,7 +1,7 @@ from datetime import datetime from unittest.mock import patch -import pytest +import pytest # noqa: F401 from szurubooru import db, model from szurubooru.func import snapshots, users @@ -144,46 +144,6 @@ def test_create(tag_factory, user_factory): assert results[0].data == "mocked" -def test_modify_saves_non_empty_diffs(post_factory, user_factory): - if "sqlite" in db.session.get_bind().driver: - pytest.xfail( - "SQLite doesn't support transaction isolation, " - "which is required to retrieve original entity" - ) - post = post_factory() - post.notes = [model.PostNote(polygon=[(0, 0), (0, 1), (1, 1)], text="old")] - user = user_factory() - db.session.add_all([post, user]) - db.session.commit() - post.source = "new source" - post.notes = [model.PostNote(polygon=[(0, 0), (0, 1), (1, 1)], text="new")] - db.session.flush() - with patch("szurubooru.func.snapshots._post_to_webhooks"): - snapshots.modify(post, user) - db.session.flush() - results = db.session.query(model.Snapshot).all() - assert len(results) == 1 - assert results[0].data == { - "type": "object change", - "value": { - "source": { - "type": "primitive change", - "old-value": None, - "new-value": "new source", - }, - "notes": { - "type": "list change", - "removed": [ - {"polygon": [[0, 0], [0, 1], [1, 1]], "text": "old"} - ], - "added": [ - {"polygon": [[0, 0], [0, 1], [1, 1]], "text": "new"} - ], - }, - }, - } - - def test_modify_doesnt_save_empty_diffs(tag_factory, user_factory): tag = tag_factory(names=["dummy"]) user = user_factory() diff --git a/server/szurubooru/tests/func/test_snapshots_transactional_isolation.py b/server/szurubooru/tests/func/test_snapshots_transactional_isolation.py new file mode 100644 index 0000000..b98cea7 --- /dev/null +++ b/server/szurubooru/tests/func/test_snapshots_transactional_isolation.py @@ -0,0 +1,59 @@ +from unittest.mock import patch + +import pytest + +from szurubooru import db, model +from szurubooru.func import snapshots + + +@pytest.fixture(autouse=True) +def session(query_logger, postgresql_db): + """ + Override db session for this specific test section only + """ + db.session = postgresql_db.session + postgresql_db.create_table(*model.Base.metadata.sorted_tables) + try: + yield postgresql_db.session + finally: + postgresql_db.reset_db() + + +def test_modify_saves_non_empty_diffs(post_factory, user_factory): + if "sqlite" in db.session.get_bind().driver: + pytest.xfail( + "SQLite doesn't support transaction isolation, " + "which is required to retrieve original entity" + ) + post = post_factory() + post.notes = [model.PostNote(polygon=[(0, 0), (0, 1), (1, 1)], text="old")] + user = user_factory() + db.session.add_all([post, user]) + db.session.commit() + post.source = "new source" + post.notes = [model.PostNote(polygon=[(0, 0), (0, 1), (1, 1)], text="new")] + db.session.flush() + with patch("szurubooru.func.snapshots._post_to_webhooks"): + snapshots.modify(post, user) + db.session.flush() + results = db.session.query(model.Snapshot).all() + assert len(results) == 1 + assert results[0].data == { + "type": "object change", + "value": { + "source": { + "type": "primitive change", + "old-value": None, + "new-value": "new source", + }, + "notes": { + "type": "list change", + "removed": [ + {"polygon": [[0, 0], [0, 1], [1, 1]], "text": "old"} + ], + "added": [ + {"polygon": [[0, 0], [0, 1], [1, 1]], "text": "new"} + ], + }, + }, + } diff --git a/server/szurubooru/tests/func/test_tag_categories.py b/server/szurubooru/tests/func/test_tag_categories.py index 11300cf..9e649a3 100644 --- a/server/szurubooru/tests/func/test_tag_categories.py +++ b/server/szurubooru/tests/func/test_tag_categories.py @@ -107,17 +107,16 @@ def test_update_category_name_reusing_other_name( tag_categories.update_category_name(category, "NAME") +@pytest.mark.parametrize("name", ["name", "NAME"]) def test_update_category_name_reusing_own_name( - config_injector, tag_category_factory + config_injector, tag_category_factory, name ): config_injector({"tag_category_name_regex": ".*"}) - for name in ["name", "NAME"]: - category = tag_category_factory(name="name") - db.session.add(category) - db.session.flush() - tag_categories.update_category_name(category, name) - assert category.name == name - db.session.rollback() + category = tag_category_factory(name="name") + db.session.add(category) + db.session.flush() + tag_categories.update_category_name(category, name) + assert category.name == name def test_update_category_color_with_empty_string(tag_category_factory): diff --git a/server/szurubooru/tests/func/test_tags.py b/server/szurubooru/tests/func/test_tags.py index ac8963c..60df122 100644 --- a/server/szurubooru/tests/func/test_tags.py +++ b/server/szurubooru/tests/func/test_tags.py @@ -513,15 +513,14 @@ def test_update_tag_names_trying_to_use_taken_name( tags.update_tag_names(tag, ["A"]) -def test_update_tag_names_reusing_own_name(config_injector, tag_factory): +@pytest.mark.parametrize("name", list("aA")) +def test_update_tag_names_reusing_own_name(config_injector, tag_factory, name): config_injector({"tag_name_regex": "^[a-zA-Z]*$"}) - for name in list("aA"): - tag = tag_factory(names=["a"]) - db.session.add(tag) - db.session.flush() - tags.update_tag_names(tag, [name]) - assert [tag_name.name for tag_name in tag.names] == [name] - db.session.rollback() + tag = tag_factory(names=["a"]) + db.session.add(tag) + db.session.flush() + tags.update_tag_names(tag, [name]) + assert [tag_name.name for tag_name in tag.names] == [name] def test_update_tag_names_changing_primary_name(config_injector, tag_factory): @@ -533,7 +532,6 @@ def test_update_tag_names_changing_primary_name(config_injector, tag_factory): db.session.flush() db.session.refresh(tag) assert [tag_name.name for tag_name in tag.names] == ["b", "a"] - db.session.rollback() @pytest.mark.parametrize("attempt", ["name", "NAME", "alias", "ALIAS"]) diff --git a/server/szurubooru/tests/search/configs/test_pool_search_config.py b/server/szurubooru/tests/search/configs/test_pool_search_config.py index 202635c..1103ec4 100644 --- a/server/szurubooru/tests/search/configs/test_pool_search_config.py +++ b/server/szurubooru/tests/search/configs/test_pool_search_config.py @@ -136,8 +136,6 @@ def test_escaping( ) db.session.flush() - if db_driver and db.session.get_bind().driver != db_driver: - pytest.xfail() if expected_pool_names is None: with pytest.raises(errors.SearchError): executor.execute(input, offset=0, limit=100) diff --git a/server/szurubooru/tests/search/configs/test_tag_search_config.py b/server/szurubooru/tests/search/configs/test_tag_search_config.py index 8175b73..9fe9a80 100644 --- a/server/szurubooru/tests/search/configs/test_tag_search_config.py +++ b/server/szurubooru/tests/search/configs/test_tag_search_config.py @@ -134,8 +134,6 @@ def test_escaping(executor, tag_factory, input, expected_tag_names, db_driver): ) db.session.flush() - if db_driver and db.session.get_bind().driver != db_driver: - pytest.xfail() if expected_tag_names is None: with pytest.raises(errors.SearchError): executor.execute(input, offset=0, limit=100) -- cgit v1.3 From 9b3123a8150faee660b4311d9d8c2c85a83b32b5 Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Mon, 29 Nov 2021 18:39:34 -0500 Subject: server: fix python docstring formatting --- server/szurubooru/facade.py | 2 +- server/szurubooru/func/auth.py | 8 ++++---- server/szurubooru/func/net.py | 2 +- server/szurubooru/func/util.py | 6 +++--- server/szurubooru/middleware/authenticator.py | 6 +++--- server/szurubooru/rest/app.py | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) (limited to 'server') diff --git a/server/szurubooru/facade.py b/server/szurubooru/facade.py index a7e4844..4c8084f 100644 --- a/server/szurubooru/facade.py +++ b/server/szurubooru/facade.py @@ -135,7 +135,7 @@ _live_migrations = ( def create_app() -> Callable[[Any, Any], Any]: - """ Create a WSGI compatible App object. """ + """Create a WSGI compatible App object.""" validate_config() coloredlogs.install(fmt="[%(asctime)-15s] %(name)s %(message)s") if config.config["debug"]: diff --git a/server/szurubooru/func/auth.py b/server/szurubooru/func/auth.py index d013775..17d25f7 100644 --- a/server/szurubooru/func/auth.py +++ b/server/szurubooru/func/auth.py @@ -25,7 +25,7 @@ RANK_MAP = OrderedDict( def get_password_hash(salt: str, password: str) -> Tuple[str, int]: - """ Retrieve argon2id password hash. """ + """Retrieve argon2id password hash.""" return ( pwhash.argon2id.str( (config.config["secret"] + salt + password).encode("utf8") @@ -37,7 +37,7 @@ def get_password_hash(salt: str, password: str) -> Tuple[str, int]: def get_sha256_legacy_password_hash( salt: str, password: str ) -> Tuple[str, int]: - """ Retrieve old-style sha256 password hash. """ + """Retrieve old-style sha256 password hash.""" digest = hashlib.sha256() digest.update(config.config["secret"].encode("utf8")) digest.update(salt.encode("utf8")) @@ -46,7 +46,7 @@ def get_sha256_legacy_password_hash( def get_sha1_legacy_password_hash(salt: str, password: str) -> Tuple[str, int]: - """ Retrieve old-style sha1 password hash. """ + """Retrieve old-style sha1 password hash.""" digest = hashlib.sha1() digest.update(b"1A2/$_4xVa") digest.update(salt.encode("utf8")) @@ -125,7 +125,7 @@ def verify_privilege(user: model.User, privilege_name: str) -> None: def generate_authentication_token(user: model.User) -> str: - """ Generate nonguessable challenge (e.g. links in password reminder). """ + """Generate nonguessable challenge (e.g. links in password reminder).""" assert user digest = hashlib.md5() digest.update(config.config["secret"].encode("utf8")) diff --git a/server/szurubooru/func/net.py b/server/szurubooru/func/net.py index 3f085a0..c53a62e 100644 --- a/server/szurubooru/func/net.py +++ b/server/szurubooru/func/net.py @@ -39,7 +39,7 @@ def download(url: str, use_video_downloader: bool = False) -> bytes: length_tally = 0 try: with urllib.request.urlopen(request) as handle: - while (chunk := handle.read(_dl_chunk_size)) : + while chunk := handle.read(_dl_chunk_size): length_tally += len(chunk) if length_tally > config.config["max_dl_filesize"]: raise DownloadTooLargeError( diff --git a/server/szurubooru/func/util.py b/server/szurubooru/func/util.py index f839136..453e121 100644 --- a/server/szurubooru/func/util.py +++ b/server/szurubooru/func/util.py @@ -83,12 +83,12 @@ def flip(source: Dict[Any, Any]) -> Dict[Any, Any]: def is_valid_email(email: Optional[str]) -> bool: - """ Return whether given email address is valid or empty. """ + """Return whether given email address is valid or empty.""" return not email or re.match(r"^[^@]*@[^@]*\.[^@]*$", email) is not None class dotdict(dict): - """ dot.notation access to dictionary attributes. """ + """dot.notation access to dictionary attributes.""" def __getattr__(self, attr: str) -> Any: return self.get(attr) @@ -98,7 +98,7 @@ class dotdict(dict): def parse_time_range(value: str) -> Tuple[datetime, datetime]: - """ Return tuple containing min/max time for given text representation. """ + """Return tuple containing min/max time for given text representation.""" one_day = timedelta(days=1) one_second = timedelta(seconds=1) almost_one_day = one_day - one_second diff --git a/server/szurubooru/middleware/authenticator.py b/server/szurubooru/middleware/authenticator.py index e73b235..436543b 100644 --- a/server/szurubooru/middleware/authenticator.py +++ b/server/szurubooru/middleware/authenticator.py @@ -7,7 +7,7 @@ from szurubooru.rest.errors import HttpBadRequest def _authenticate_basic_auth(username: str, password: str) -> model.User: - """ Try to authenticate user. Throw AuthError for invalid users. """ + """Try to authenticate user. Throw AuthError for invalid users.""" user = users.get_user_by_name(username) if not auth.is_valid_password(user, password): raise errors.AuthError("Invalid password.") @@ -17,7 +17,7 @@ def _authenticate_basic_auth(username: str, password: str) -> model.User: def _authenticate_token( username: str, token: str ) -> Tuple[model.User, model.UserToken]: - """ Try to authenticate user. Throw AuthError for invalid users. """ + """Try to authenticate user. Throw AuthError for invalid users.""" user = users.get_user_by_name(username) user_token = user_tokens.get_by_user_and_token(user, token) if not auth.is_valid_token(user_token): @@ -72,7 +72,7 @@ def _get_user(ctx: rest.Context, bump_login: bool) -> Optional[model.User]: def process_request(ctx: rest.Context) -> None: - """ Bind the user to request. Update last login time if needed. """ + """Bind the user to request. Update last login time if needed.""" bump_login = ctx.get_param_as_bool("bump-login", default=False) auth_user = _get_user(ctx, bump_login) if auth_user: diff --git a/server/szurubooru/rest/app.py b/server/szurubooru/rest/app.py index a6f10fb..c098bd0 100644 --- a/server/szurubooru/rest/app.py +++ b/server/szurubooru/rest/app.py @@ -11,7 +11,7 @@ from szurubooru.rest import context, errors, middleware, routes def _json_serializer(obj: Any) -> str: - """ JSON serializer for objects not serializable by default JSON code """ + """JSON serializer for objects not serializable by default JSON code""" if isinstance(obj, datetime): serial = obj.isoformat("T") + "Z" return serial -- cgit v1.3 From 106dcc41356fdf7671a3b2a98a5fe3023c0e7af9 Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Sun, 16 Jan 2022 11:07:46 -0500 Subject: server/func/images: Do not pass file content to ffmpeg stdin --- server/szurubooru/func/images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/szurubooru/func/images.py b/server/szurubooru/func/images.py index de41222..4d4011f 100644 --- a/server/szurubooru/func/images.py +++ b/server/szurubooru/func/images.py @@ -277,10 +277,10 @@ class Image: proc = subprocess.Popen( cli, stdout=subprocess.PIPE, - stdin=subprocess.PIPE, + stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, ) - out, err = proc.communicate(input=self.content) + out, err = proc.communicate() if proc.returncode != 0: logger.warning( "Failed to execute ffmpeg command (cli=%r, err=%r)", -- cgit v1.3 From a22485afda0a37ac1074f5bfea10828c8fef3151 Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Mon, 7 Feb 2022 12:51:25 -0500 Subject: server/func/images: upgrade to heif-image-plugin --- server/Dockerfile | 2 +- server/requirements.txt | 14 +++++++------- server/szurubooru/func/image_hash.py | 5 +---- server/szurubooru/func/images.py | 2 ++ 4 files changed, 11 insertions(+), 12 deletions(-) (limited to 'server') diff --git a/server/Dockerfile b/server/Dockerfile index 205c8e4..aac0a65 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -28,9 +28,9 @@ RUN apk --no-cache add \ && pip3 install --no-cache-dir --disable-pip-version-check \ alembic \ "coloredlogs==5.0" \ + heif-image-plugin \ youtube_dl \ pillow-avif-plugin \ - pyheif-pillow-opener \ && apk --no-cache del py3-pip COPY ./ /opt/app/ diff --git a/server/requirements.txt b/server/requirements.txt index 2a09b24..6b032d3 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -1,14 +1,14 @@ alembic>=0.8.5 -pyyaml>=3.11 -psycopg2-binary>=2.6.1 -SQLAlchemy>=1.0.12, <1.4 -coloredlogs==5.0 certifi>=2017.11.5 +coloredlogs==5.0 +heif-image-plugin>=0.3.2 numpy>=1.8.2 +pillow-avif-plugin>=1.1.0 pillow>=4.3.0 +psycopg2-binary>=2.6.1 pynacl>=1.2.1 -pytz>=2018.3 pyRFC3339>=1.0 -pillow-avif-plugin>=1.1.0 -pyheif-pillow-opener>=0.1.0 +pytz>=2018.3 +pyyaml>=3.11 +SQLAlchemy>=1.0.12, <1.4 youtube_dl diff --git a/server/szurubooru/func/image_hash.py b/server/szurubooru/func/image_hash.py index 05b27a4..76d5a84 100644 --- a/server/szurubooru/func/image_hash.py +++ b/server/szurubooru/func/image_hash.py @@ -4,16 +4,13 @@ from datetime import datetime from io import BytesIO from typing import Any, Callable, List, Optional, Set, Tuple +import HeifImagePlugin import numpy as np import pillow_avif -import pyheif from PIL import Image -from pyheif_pillow_opener import register_heif_opener from szurubooru import config, errors -register_heif_opener() - logger = logging.getLogger(__name__) # Math based on paper from H. Chi Wong, Marshall Bern and David Goldberg diff --git a/server/szurubooru/func/images.py b/server/szurubooru/func/images.py index 4d4011f..e135d18 100644 --- a/server/szurubooru/func/images.py +++ b/server/szurubooru/func/images.py @@ -7,6 +7,8 @@ import subprocess from io import BytesIO from typing import List +import HeifImagePlugin +import pillow_avif from PIL import Image as PILImage from szurubooru import errors -- cgit v1.3 From 6de0a742570058365753266d6b8f43b354de9b11 Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Mon, 7 Feb 2022 16:44:56 -0500 Subject: server/config: fix deprecated database string format --- server/szurubooru/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/szurubooru/config.py b/server/szurubooru/config.py index 1515a54..8f87642 100644 --- a/server/szurubooru/config.py +++ b/server/szurubooru/config.py @@ -33,7 +33,7 @@ def _docker_config() -> Dict: "show_sql": int(os.getenv("LOG_SQL", 0)), "data_url": os.getenv("DATA_URL", "data/"), "data_dir": "/data/", - "database": "postgres://%(user)s:%(pass)s@%(host)s:%(port)d/%(db)s" + "database": "postgresql://%(user)s:%(pass)s@%(host)s:%(port)d/%(db)s" % { "user": os.getenv("POSTGRES_USER"), "pass": os.getenv("POSTGRES_PASSWORD"), -- cgit v1.3 From 82541536afd95ebbbb72b1983caae77b67eb8b2d Mon Sep 17 00:00:00 2001 From: noirscape Date: Sat, 12 Feb 2022 22:16:13 +0100 Subject: Make waitress thread count configurable. This should fix most scaling problems without needing to start more server instances. By default, waitress maintains at most 4 threads. This works fine if the database is small (sub 100k posts) but causes a large Task queue depth to occur if the database is larger. Letting users increase the amount of threads means that one server instance is able to handle more requests without locking up the rest of the site. This adds a new environment variable to .env, THREADS, which can be used to configure the amount of threads to start and is by default set to 4 (the default amount used by waitress). --- doc/example.env | 6 ++++++ docker-compose.yml | 1 + server/Dockerfile | 3 +++ server/docker-start.sh | 4 ++-- 4 files changed, 12 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/doc/example.env b/doc/example.env index 59e1e85..303a25e 100644 --- a/doc/example.env +++ b/doc/example.env @@ -10,6 +10,12 @@ BUILD_INFO=latest # otherwise the port specified here will be publicly accessible PORT=8080 +# How many waitress threads to start +# 4 is the default amount of threads. If you experience performance +# degradation with a large number of posts, increasing this may +# improve performance, since waitress is most likely clogging up with Tasks. +THREADS=4 + # URL base to run szurubooru under # See "Additional Features" section in INSTALL.md BASE_URL=/ diff --git a/docker-compose.yml b/docker-compose.yml index 1da23bd..38e08b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: #POSTGRES_DB: defaults to same as POSTGRES_USER #POSTGRES_PORT: 5432 #LOG_SQL: 0 (1 for verbose SQL logs) + THREADS: volumes: - "${MOUNT_DATA}:/data" - "./server/config.yaml:/opt/app/config.yaml" diff --git a/server/Dockerfile b/server/Dockerfile index aac0a65..a13e230 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -83,6 +83,9 @@ ARG PORT=6666 ENV PORT=${PORT} EXPOSE ${PORT} +ARG THREADS=4 +ENV THREADS=${THREADS} + VOLUME ["/data/"] ARG DOCKER_REPO diff --git a/server/docker-start.sh b/server/docker-start.sh index 34a0e49..eebef1c 100755 --- a/server/docker-start.sh +++ b/server/docker-start.sh @@ -4,5 +4,5 @@ cd /opt/app alembic upgrade head -echo "Starting szurubooru API on port ${PORT}" -exec waitress-serve-3 --port ${PORT} szurubooru.facade:app +echo "Starting szurubooru API on port ${PORT} - Running on ${THREADS} threads" +exec waitress-serve-3 --port ${PORT} --threads ${THREADS} szurubooru.facade:app -- cgit v1.3 From 6088e89ea1258ddd7686370355ca2ce4a026f759 Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Wed, 30 Mar 2022 23:04:16 -0400 Subject: server/szuru-admin: Add thumbnail regeneration script Closes #467 --- .pre-commit-config.yaml | 2 +- server/szuru-admin | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c2e4d53..7b550ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: remove-tabs - repo: https://github.com/psf/black - rev: 21.11b1 + rev: '22.3.0' hooks: - id: black files: 'server/' diff --git a/server/szuru-admin b/server/szuru-admin index 004a751..08ba182 100755 --- a/server/szuru-admin +++ b/server/szuru-admin @@ -91,6 +91,15 @@ def reset_filenames() -> None: rename_in_dir("posts/custom-thumbnails/") +def regenerate_thumbnails() -> None: + for post in db.session.query(model.Post).all(): + print("Generating tumbnail for post %d ..." % post.post_id, end="\r") + try: + postfuncs.generate_post_thumbnail(post) + except Exception: + pass + + def main() -> None: parser_top = ArgumentParser( description="Collection of CLI commands for an administrator to use", @@ -114,6 +123,12 @@ def main() -> None: help="reset and rename the content and thumbnail " "filenames in case of a lost/changed secret key", ) + parser.add_argument( + "--regenerate-thumbnails", + action="store_true", + help="regenerate the thumbnails for posts if the " + "thumbnail files are missing", + ) command = parser_top.parse_args() try: @@ -123,6 +138,8 @@ def main() -> None: check_audio() elif command.reset_filenames: reset_filenames() + elif command.regenerate_thumbnails: + regenerate_thumbnails() except errors.BaseError as e: print(e, file=stderr) -- cgit v1.3 From e746f09911cbe1c94055296310a96ab057daecdb Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Thu, 31 Mar 2022 18:43:37 -0400 Subject: server: fix build error due to broken pip requirements Pinned pyheif to v0.6.1 --- server/Dockerfile | 20 ++++++++++---------- server/requirements.txt | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) (limited to 'server') diff --git a/server/Dockerfile b/server/Dockerfile index a13e230..487f192 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -7,8 +7,13 @@ WORKDIR /opt/app RUN apk --no-cache add \ python3 \ python3-dev \ - ffmpeg \ py3-pip \ + build-base \ + libheif \ + libheif-dev \ + libavif \ + libavif-dev \ + ffmpeg \ # from requirements.txt: py3-yaml \ py3-psycopg2 \ @@ -19,18 +24,13 @@ RUN apk --no-cache add \ py3-pynacl \ py3-tz \ py3-pyrfc3339 \ - build-base \ - && apk --no-cache add \ - libheif \ - libavif \ - libheif-dev \ - libavif-dev \ && pip3 install --no-cache-dir --disable-pip-version-check \ - alembic \ + "alembic>=0.8.5" \ "coloredlogs==5.0" \ - heif-image-plugin \ + "pyheif==0.6.1" \ + "heif-image-plugin>=0.3.2" \ youtube_dl \ - pillow-avif-plugin \ + "pillow-avif-plugin>=1.1.0" \ && apk --no-cache del py3-pip COPY ./ /opt/app/ diff --git a/server/requirements.txt b/server/requirements.txt index 6b032d3..16b29ff 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -1,11 +1,12 @@ alembic>=0.8.5 certifi>=2017.11.5 coloredlogs==5.0 -heif-image-plugin>=0.3.2 +heif-image-plugin==0.3.2 numpy>=1.8.2 pillow-avif-plugin>=1.1.0 pillow>=4.3.0 psycopg2-binary>=2.6.1 +pyheif==0.6.1 pynacl>=1.2.1 pyRFC3339>=1.0 pytz>=2018.3 -- cgit v1.3 From 8088ff3bbe101907c2cea3341bc4437285b3d12d Mon Sep 17 00:00:00 2001 From: w1kl4s Date: Sat, 10 Sep 2022 08:44:16 +0200 Subject: support ftypiso6 file signature --- server/szurubooru/func/mime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/szurubooru/func/mime.py b/server/szurubooru/func/mime.py index 3be43f7..ee10b9d 100644 --- a/server/szurubooru/func/mime.py +++ b/server/szurubooru/func/mime.py @@ -36,7 +36,7 @@ def get_mime_type(content: bytes) -> str: if content[0:4] == b"\x1A\x45\xDF\xA3": return "video/webm" - if content[4:12] in (b"ftypisom", b"ftypiso5", b"ftypmp42", b"ftypM4V "): + if content[4:12] in (b"ftypisom", b"ftypiso5", b"ftypiso6", b"ftypmp42", b"ftypM4V "): return "video/mp4" return "application/octet-stream" -- cgit v1.3 From e3062b1c77b52ff9fad23a279844b946d6491417 Mon Sep 17 00:00:00 2001 From: Neo <50623835+neobooru@users.noreply.github.com> Date: Thu, 19 Jan 2023 18:44:31 +0100 Subject: client: add bulk delete feature (#459) This introduces a new privilege 'posts:bulk-edit:delete' which by default is given to power users. --- client/css/post-list-view.styl | 37 ++++++++++++++++++++- client/html/posts_header.tpl | 7 ++++ client/html/posts_page.tpl | 4 +++ client/js/controllers/post_list_controller.js | 46 +++++++++++++++++++++++++++ client/js/views/posts_header_view.js | 45 ++++++++++++++++++++++++++ client/js/views/posts_page_view.js | 35 ++++++++++++++++++++ server/config.yaml.dist | 1 + 7 files changed, 174 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/client/css/post-list-view.styl b/client/css/post-list-view.styl index 0272ee1..7f6aa80 100644 --- a/client/css/post-list-view.styl +++ b/client/css/post-list-view.styl @@ -114,6 +114,29 @@ &[data-disabled] background: rgba(200, 200, 200, 0.7) + .delete-flipper + display: inline-block + padding: 0.5em + box-sizing: border-box + border: 0 + &:after + display: inline-block + width: 1em + height: 1em + text-align: center + line-height: 1em + font-size: 2.2em + &.delete + background: rgba(255, 0, 0, 0.7) + &:after + color: white + font-family: FontAwesome; + content: "\f1f8"; // fa-trash + &:not(.delete) + background: rgba(200, 200, 200, 0.7) + &:after + color: white + content: '-' .thumbnail width: 100% @@ -215,7 +238,19 @@ .append @media (max-width: 1000px) margin-left: 0 - + .bulk-edit-delete + &.opened + .start + @media (max-width: 1000px) + margin-left: 0 + &:not(.opened) + .start + display: none + .append.open + @media (max-width: 1000px) + margin-left: 0 + .start + margin-left: 1em .safety margin-right: 0.25em &.safety-safe diff --git a/client/html/posts_header.tpl b/client/html/posts_header.tpl index e0ba0ea..d1422d2 100644 --- a/client/html/posts_header.tpl +++ b/client/html/posts_header.tpl @@ -28,4 +28,11 @@ %>Stop editing safety<% %><% %><% } %><% + %><% if (ctx.canBulkDelete) { %><% + %>
<% + %>Mass delete<% + %><% + %>Stop deleting<% + %>
<% + %><% } %><% %> diff --git a/client/html/posts_page.tpl b/client/html/posts_page.tpl index 7836278..52011ad 100644 --- a/client/html/posts_page.tpl +++ b/client/html/posts_page.tpl @@ -50,6 +50,10 @@ <% } %> <% } %> + <% if (ctx.canBulkDelete && ctx.parameters && ctx.parameters.delete) { %> + + + <% } %> <% } %> diff --git a/client/js/controllers/post_list_controller.js b/client/js/controllers/post_list_controller.js index 526d8f5..ec3e13c 100644 --- a/client/js/controllers/post_list_controller.js +++ b/client/js/controllers/post_list_controller.js @@ -44,6 +44,7 @@ class PostListController { enableSafety: api.safetyEnabled(), canBulkEditTags: api.hasPrivilege("posts:bulk-edit:tags"), canBulkEditSafety: api.hasPrivilege("posts:bulk-edit:safety"), + canBulkDelete: api.hasPrivilege("posts:bulk-edit:delete"), bulkEdit: { tags: this._bulkEditTags, }, @@ -52,6 +53,14 @@ class PostListController { this._evtNavigate(e) ); + this._headerView._bulkDeleteEditor.addEventListener( + "deleteSelectedPosts", + (e) => { + this._evtDeleteSelectedPosts(e); + } + ); + + this._postsMarkedForDeletion = []; this._syncPageController(); } @@ -91,6 +100,38 @@ class PostListController { e.detail.post.save().catch((error) => window.alert(error.message)); } + _evtMarkForDeletion(e) { + const postId = e.detail; + + // Add or remove post from delete list + if (e.detail.delete) { + this._postsMarkedForDeletion.push(e.detail.post); + } else { + this._postsMarkedForDeletion = this._postsMarkedForDeletion.filter( + (x) => x.id != e.detail.post.id + ); + } + } + + _evtDeleteSelectedPosts(e) { + if (this._postsMarkedForDeletion.length == 0) return; + + if ( + confirm( + `Are you sure you want to delete ${this._postsMarkedForDeletion.length} posts?` + ) + ) { + Promise.all( + this._postsMarkedForDeletion.map((post) => post.delete()) + ) + .catch((error) => window.alert(error.message)) + .then(() => { + this._postsMarkedForDeletion = []; + this._headerView._navigate(); + }); + } + } + _syncPageController() { this._pageController.run({ parameters: this._ctx.parameters, @@ -117,8 +158,10 @@ class PostListController { canBulkEditSafety: api.hasPrivilege( "posts:bulk-edit:safety" ), + canBulkDelete: api.hasPrivilege("posts:bulk-edit:delete"), bulkEdit: { tags: this._bulkEditTags, + markedForDeletion: this._postsMarkedForDeletion, }, postFlow: settings.get().postFlow, }); @@ -128,6 +171,9 @@ class PostListController { view.addEventListener("changeSafety", (e) => this._evtChangeSafety(e) ); + view.addEventListener("markForDeletion", (e) => + this._evtMarkForDeletion(e) + ); return view; }, }); diff --git a/client/js/views/posts_header_view.js b/client/js/views/posts_header_view.js index f64060d..38a4aa9 100644 --- a/client/js/views/posts_header_view.js +++ b/client/js/views/posts_header_view.js @@ -141,6 +141,34 @@ class BulkTagEditor extends BulkEditor { } } +class BulkDeleteEditor extends BulkEditor { + constructor(hostNode) { + super(hostNode); + this._hostNode.addEventListener("submit", (e) => + this._evtFormSubmit(e) + ); + } + + _evtFormSubmit(e) { + e.preventDefault(); + this.dispatchEvent( + new CustomEvent("deleteSelectedPosts", { detail: {} }) + ); + } + + _evtOpenLinkClick(e) { + e.preventDefault(); + this.toggleOpen(true); + this.dispatchEvent(new CustomEvent("open", { detail: {} })); + } + + _evtCloseLinkClick(e) { + e.preventDefault(); + this.toggleOpen(false); + this.dispatchEvent(new CustomEvent("close", { detail: {} })); + } +} + class PostsHeaderView extends events.EventTarget { constructor(ctx) { super(); @@ -186,6 +214,13 @@ class PostsHeaderView extends events.EventTarget { this._bulkEditors.push(this._bulkSafetyEditor); } + if (this._bulkEditDeleteNode) { + this._bulkDeleteEditor = new BulkDeleteEditor( + this._bulkEditDeleteNode + ); + this._bulkEditors.push(this._bulkDeleteEditor); + } + for (let editor of this._bulkEditors) { editor.addEventListener("submit", (e) => { this._navigate(); @@ -204,6 +239,8 @@ class PostsHeaderView extends events.EventTarget { this._openBulkEditor(this._bulkTagEditor); } else if (ctx.parameters.safety && this._bulkSafetyEditor) { this._openBulkEditor(this._bulkSafetyEditor); + } else if (ctx.parameters.delete && this._bulkDeleteEditor) { + this._openBulkEditor(this._bulkDeleteEditor); } } @@ -227,6 +264,10 @@ class PostsHeaderView extends events.EventTarget { return this._hostNode.querySelector(".bulk-edit-safety"); } + get _bulkEditDeleteNode() { + return this._hostNode.querySelector(".bulk-edit-delete"); + } + _openBulkEditor(editor) { editor.toggleOpen(true); this._hideBulkEditorsExcept(editor); @@ -293,6 +334,10 @@ class PostsHeaderView extends events.EventTarget { this._bulkSafetyEditor && this._bulkSafetyEditor.opened ? "1" : null; + parameters.delete = + this._bulkDeleteEditor && this._bulkDeleteEditor.opened + ? "1" + : null; this.dispatchEvent( new CustomEvent("navigate", { detail: { parameters: parameters } }) ); diff --git a/client/js/views/posts_page_view.js b/client/js/views/posts_page_view.js index ba07a63..c4b1988 100644 --- a/client/js/views/posts_page_view.js +++ b/client/js/views/posts_page_view.js @@ -39,6 +39,13 @@ class PostsPageView extends events.EventTarget { ); } } + + const deleteFlipperNode = this._getDeleteFlipperNode(listItemNode); + if (deleteFlipperNode) { + deleteFlipperNode.addEventListener("click", (e) => + this._evtBulkToggleDeleteClick(e, post) + ); + } } this._syncBulkEditorsHighlights(); @@ -56,6 +63,10 @@ class PostsPageView extends events.EventTarget { return listItemNode.querySelector(".safety-flipper"); } + _getDeleteFlipperNode(listItemNode) { + return listItemNode.querySelector(".delete-flipper"); + } + _evtPostChange(e) { const listItemNode = this._postIdToListItemNode[e.detail.post.id]; for (let node of listItemNode.querySelectorAll("[data-disabled]")) { @@ -99,6 +110,20 @@ class PostsPageView extends events.EventTarget { ); } + _evtBulkToggleDeleteClick(e, post) { + e.preventDefault(); + const linkNode = e.target; + linkNode.classList.toggle("delete"); + this.dispatchEvent( + new CustomEvent("markForDeletion", { + detail: { + post, + delete: linkNode.classList.contains("delete"), + }, + }) + ); + } + _syncBulkEditorsHighlights() { for (let listItemNode of this._listItemNodes) { const postId = listItemNode.getAttribute("data-post-id"); @@ -123,6 +148,16 @@ class PostsPageView extends events.EventTarget { ); } } + + const deleteFlipperNode = this._getDeleteFlipperNode(listItemNode); + if (deleteFlipperNode) { + deleteFlipperNode.classList.toggle( + "delete", + this._ctx.bulkEdit.markedForDeletion.some( + (x) => x.id == postId + ) + ); + } } } } diff --git a/server/config.yaml.dist b/server/config.yaml.dist index bc4e363..193aac3 100644 --- a/server/config.yaml.dist +++ b/server/config.yaml.dist @@ -115,6 +115,7 @@ privileges: 'posts:favorite': regular 'posts:bulk-edit:tags': power 'posts:bulk-edit:safety': power + 'posts:bulk-edit:delete': power 'tags:create': regular 'tags:edit:names': power -- cgit v1.3 From 244a0f0b6c190a50946a6fed37c9768f151f0d29 Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Sun, 5 Feb 2023 12:25:30 -0500 Subject: server/test: skip network tests by default --- server/szurubooru/tests/func/test_net.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/szurubooru/tests/func/test_net.py b/server/szurubooru/tests/func/test_net.py index c5b4c73..be2f3c9 100644 --- a/server/szurubooru/tests/func/test_net.py +++ b/server/szurubooru/tests/func/test_net.py @@ -1,3 +1,5 @@ +import os + import pytest from szurubooru import errors @@ -16,6 +18,9 @@ def inject_config(tmpdir, config_injector): ) +@pytest.mark.skipif( + "TEST_NET" not in os.environ, reason="Network tests skipped by default." +) def test_download(): url = "http://info.cern.ch/hypertext/WWW/TheProject.html" @@ -62,6 +67,9 @@ def test_download(): assert actual_content == expected_content +@pytest.mark.skipif( + "TEST_NET" not in os.environ, reason="Network tests skipped by default." +) @pytest.mark.parametrize( "url", [ @@ -74,6 +82,9 @@ def test_too_large_download(url): net.download(url, use_video_downloader=True) +@pytest.mark.skipif( + "TEST_NET" not in os.environ, reason="Network tests skipped by default." +) @pytest.mark.parametrize( "url,expected_sha1", [ @@ -96,6 +107,9 @@ def test_content_download(url, expected_sha1): assert get_sha1(actual_content) == expected_sha1 +@pytest.mark.skipif( + "TEST_NET" not in os.environ, reason="Network tests skipped by default." +) def test_bad_content_downlaod(): url = "http://info.cern.ch/hypertext/WWW/TheProject.html" with pytest.raises(errors.ThirdPartyError): @@ -108,11 +122,13 @@ def test_no_webhooks(config_injector): assert len(res) == 0 +@pytest.mark.skipif( + "TEST_NET" not in os.environ, reason="Network tests skipped by default." +) @pytest.mark.parametrize( "webhook,status_code", [ ("https://postman-echo.com/post", 200), - ("http://localhost/", 400), ("https://postman-echo.com/get", 400), ], ) @@ -121,6 +137,9 @@ def test_single_webhook(config_injector, webhook, status_code): assert ret == status_code +@pytest.mark.skipif( + "TEST_NET" not in os.environ, reason="Network tests skipped by default." +) def test_multiple_webhooks(config_injector): config_injector( { -- cgit v1.3 From 8a03015349c3d9642c4d83472dbe76b072dd05a1 Mon Sep 17 00:00:00 2001 From: skybldev Date: Fri, 5 Aug 2022 21:31:27 -0400 Subject: client+server: added quicktime upload support --- client/html/post_merge_side.tpl | 1 + client/html/post_readonly_sidebar.tpl | 1 + client/js/views/post_upload_view.js | 2 ++ server/szurubooru/func/mime.py | 6 +++++- 4 files changed, 9 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/client/html/post_merge_side.tpl b/client/html/post_merge_side.tpl index fe70502..a08070f 100644 --- a/client/html/post_merge_side.tpl +++ b/client/html/post_merge_side.tpl @@ -42,6 +42,7 @@ 'image/heic': 'HEIC', 'video/webm': 'WEBM', 'video/mp4': 'MPEG-4', + 'video/quicktime': 'MOV', 'application/x-shockwave-flash': 'SWF', }[ctx.post.mimeType] + ' (' + diff --git a/client/html/post_readonly_sidebar.tpl b/client/html/post_readonly_sidebar.tpl index 4f18624..0f93ae3 100644 --- a/client/html/post_readonly_sidebar.tpl +++ b/client/html/post_readonly_sidebar.tpl @@ -15,6 +15,7 @@ 'image/heic': 'HEIC', 'video/webm': 'WEBM', 'video/mp4': 'MPEG-4', + 'video/quicktime': 'MOV', 'application/x-shockwave-flash': 'SWF', }[ctx.post.mimeType] %> diff --git a/client/js/views/post_upload_view.js b/client/js/views/post_upload_view.js index fc98a19..4ef4c1a 100644 --- a/client/js/views/post_upload_view.js +++ b/client/js/views/post_upload_view.js @@ -22,6 +22,7 @@ function _mimeTypeToPostType(mimeType) { "image/heic": "image", "video/mp4": "video", "video/webm": "video", + "video/quicktime": "video", }[mimeType] || "unknown" ); } @@ -120,6 +121,7 @@ class Url extends Uploadable { heif: "image/heif", heic: "image/heic", mp4: "video/mp4", + mov: "video/quicktime", webm: "video/webm", }; for (let extension of Object.keys(mime)) { diff --git a/server/szurubooru/func/mime.py b/server/szurubooru/func/mime.py index 3be43f7..4aa9c3d 100644 --- a/server/szurubooru/func/mime.py +++ b/server/szurubooru/func/mime.py @@ -39,6 +39,9 @@ def get_mime_type(content: bytes) -> str: if content[4:12] in (b"ftypisom", b"ftypiso5", b"ftypmp42", b"ftypM4V "): return "video/mp4" + if content[4:12] in (b"ftypqt "): + return "video/quicktime" + return "application/octet-stream" @@ -54,6 +57,7 @@ def get_extension(mime_type: str) -> Optional[str]: "image/heif": "heif", "image/heic": "heic", "video/mp4": "mp4", + "video/quicktime": "mov", "video/webm": "webm", "application/octet-stream": "dat", } @@ -65,7 +69,7 @@ def is_flash(mime_type: str) -> bool: def is_video(mime_type: str) -> bool: - return mime_type.lower() in ("application/ogg", "video/mp4", "video/webm") + return mime_type.lower() in ("application/ogg", "video/mp4", "video/quicktime", "video/webm") def is_image(mime_type: str) -> bool: -- cgit v1.3 From 42524503b9fb5a73c69402c435e8d7aee3e036fa Mon Sep 17 00:00:00 2001 From: Shyam Sunder Date: Mon, 17 Apr 2023 11:58:13 -0400 Subject: client/tests: add unit tests for quicktime videos --- server/szurubooru/func/mime.py | 9 +++++++-- server/szurubooru/tests/assets/mov.mov | Bin 0 -> 844 bytes server/szurubooru/tests/func/test_mime.py | 4 ++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 server/szurubooru/tests/assets/mov.mov (limited to 'server') diff --git a/server/szurubooru/func/mime.py b/server/szurubooru/func/mime.py index 4aa9c3d..f01af0d 100644 --- a/server/szurubooru/func/mime.py +++ b/server/szurubooru/func/mime.py @@ -39,7 +39,7 @@ def get_mime_type(content: bytes) -> str: if content[4:12] in (b"ftypisom", b"ftypiso5", b"ftypmp42", b"ftypM4V "): return "video/mp4" - if content[4:12] in (b"ftypqt "): + if content[4:12] == b"ftypqt ": return "video/quicktime" return "application/octet-stream" @@ -69,7 +69,12 @@ def is_flash(mime_type: str) -> bool: def is_video(mime_type: str) -> bool: - return mime_type.lower() in ("application/ogg", "video/mp4", "video/quicktime", "video/webm") + return mime_type.lower() in ( + "application/ogg", + "video/mp4", + "video/quicktime", + "video/webm", + ) def is_image(mime_type: str) -> bool: diff --git a/server/szurubooru/tests/assets/mov.mov b/server/szurubooru/tests/assets/mov.mov new file mode 100644 index 0000000..911ee85 Binary files /dev/null and b/server/szurubooru/tests/assets/mov.mov differ diff --git a/server/szurubooru/tests/func/test_mime.py b/server/szurubooru/tests/func/test_mime.py index b33746b..551ba7c 100644 --- a/server/szurubooru/tests/func/test_mime.py +++ b/server/szurubooru/tests/func/test_mime.py @@ -7,6 +7,7 @@ from szurubooru.func import mime "input_path,expected_mime_type", [ ("mp4.mp4", "video/mp4"), + ("mov.mov", "video/quicktime"), ("webm.webm", "video/webm"), ("flash.swf", "application/x-shockwave-flash"), ("png.png", "image/png"), @@ -35,6 +36,7 @@ def test_get_mime_type_for_empty_file(): [ ("video/mp4", "mp4"), ("video/webm", "webm"), + ("video/quicktime", "mov"), ("application/x-shockwave-flash", "swf"), ("image/png", "png"), ("image/jpeg", "jpg"), @@ -70,6 +72,8 @@ def test_is_flash(input_mime_type, expected_state): ("VIDEO/WEBM", True), ("video/mp4", True), ("VIDEO/MP4", True), + ("video/quicktime", True), + ("VIDEO/QUICKTIME", True), ("video/anything_else", False), ("application/ogg", True), ("not a video", False), -- cgit v1.3 From 4806bbe0eda3a6c2f76800200a60d7f19971150c Mon Sep 17 00:00:00 2001 From: neobooru <50623835+neobooru@users.noreply.github.com> Date: Thu, 7 Oct 2021 16:28:06 +0200 Subject: server: post category filter --- .../search/configs/post_search_config.py | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'server') diff --git a/server/szurubooru/search/configs/post_search_config.py b/server/szurubooru/search/configs/post_search_config.py index ddc003b..8d4672d 100644 --- a/server/szurubooru/search/configs/post_search_config.py +++ b/server/szurubooru/search/configs/post_search_config.py @@ -122,6 +122,34 @@ def _pool_filter( )(query, criterion, negated) +def _category_filter( + query: SaQuery, criterion: Optional[criteria.BaseCriterion], negated: bool +) -> SaQuery: + assert criterion + + # Step 1. find the id for the category + q1 = db.session.query(model.TagCategory.tag_category_id).filter( + model.TagCategory.name == criterion.value + ) + + # Step 2. find the tags with that category + q2 = db.session.query(model.Tag.tag_id).filter( + model.Tag.category_id.in_(q1) + ) + + # Step 3. find all posts that have at least one of those tags + q3 = db.session.query(model.PostTag.post_id).filter( + model.PostTag.tag_id.in_(q2) + ) + + # Step 4. profit + expr = model.Post.post_id.in_(q3) + if negated: + expr = ~expr + + return query.filter(expr) + + class PostSearchConfig(BaseSearchConfig): def __init__(self) -> None: self.user = None # type: Optional[model.User] @@ -349,6 +377,7 @@ class PostSearchConfig(BaseSearchConfig): ), ), (["pool"], _pool_filter), + (["category"], _category_filter), ] ) -- cgit v1.3 From 7a82e9d5813d8b88e2f49ebdabbf19957b2f393a Mon Sep 17 00:00:00 2001 From: neobooru <50623835+neobooru@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:32:41 +0200 Subject: tests/server: post category filter --- .../search/configs/test_post_search_config.py | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) (limited to 'server') diff --git a/server/szurubooru/tests/search/configs/test_post_search_config.py b/server/szurubooru/tests/search/configs/test_post_search_config.py index 4fb8191..b86fa27 100644 --- a/server/szurubooru/tests/search/configs/test_post_search_config.py +++ b/server/szurubooru/tests/search/configs/test_post_search_config.py @@ -863,3 +863,55 @@ def test_tumbleweed( db.session.flush() verify_unpaged("special:tumbleweed", [4]) verify_unpaged("-special:tumbleweed", [1, 2, 3]) + + +@pytest.mark.parametrize( + "input,expected_post_ids", + [ + ("category:cat1", [1, 2, 3]), + ("category:cat2", [3, 4]), + ], +) +def test_search_by_tag_category( + verify_unpaged, + post_factory, + tag_factory, + tag_category_factory, + input, + expected_post_ids, +): + cat1 = tag_category_factory(name="cat1") + cat2 = tag_category_factory(name="cat2") + tag1 = tag_factory(names=["t1"], category=cat1) + tag2 = tag_factory(names=["t2"], category=cat1) + tag3 = tag_factory(names=["t3"], category=cat2) + + post1 = post_factory(id=1) + post1.tags.append(tag1) + + post2 = post_factory(id=2) + post2.tags.append(tag2) + + post3 = post_factory(id=3) + post3.tags.append(tag1) + post3.tags.append(tag3) + + post4 = post_factory(id=4) + post4.tags.append(tag3) + + post5 = post_factory(id=5) + + db.session.add_all( + [ + tag1, + tag2, + tag3, + post1, + post2, + post3, + post4, + post5, + ] + ) + db.session.flush() + verify_unpaged(input, expected_post_ids) -- cgit v1.3 From c292b96f068ec2e6323466bed011d5bae9ede662 Mon Sep 17 00:00:00 2001 From: "Zak B. Elep" Date: Thu, 17 Aug 2023 20:41:50 +0800 Subject: server/net: use yt-dlp instead of youtube-dl youtube-dl no longer even gets URLs properly, so switch to yt-dlp as a drop-in replacement for it. --- server/Dockerfile | 2 +- server/requirements.txt | 2 +- server/szurubooru/func/net.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/Dockerfile b/server/Dockerfile index 487f192..c2640f1 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -29,7 +29,7 @@ RUN apk --no-cache add \ "coloredlogs==5.0" \ "pyheif==0.6.1" \ "heif-image-plugin>=0.3.2" \ - youtube_dl \ + yt-dlp \ "pillow-avif-plugin>=1.1.0" \ && apk --no-cache del py3-pip diff --git a/server/requirements.txt b/server/requirements.txt index 16b29ff..ceff0b8 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -12,4 +12,4 @@ pyRFC3339>=1.0 pytz>=2018.3 pyyaml>=3.11 SQLAlchemy>=1.0.12, <1.4 -youtube_dl +yt-dlp diff --git a/server/szurubooru/func/net.py b/server/szurubooru/func/net.py index c53a62e..d6aa95e 100644 --- a/server/szurubooru/func/net.py +++ b/server/szurubooru/func/net.py @@ -64,7 +64,7 @@ def download(url: str, use_video_downloader: bool = False) -> bytes: def _get_youtube_dl_content_url(url: str) -> str: - cmd = ["youtube-dl", "--format", "best", "--no-playlist"] + cmd = ["yt-dlp", "--format", "best", "--no-playlist"] if config.config["user_agent"]: cmd.extend(["--user-agent", config.config["user_agent"]]) cmd.extend(["--get-url", url]) -- cgit v1.3 From da425afc492d402c675aaabaf12a980f99453305 Mon Sep 17 00:00:00 2001 From: neobooru <50623835+neobooru@users.noreply.github.com> Date: Wed, 21 Feb 2024 01:46:28 +0100 Subject: Pin pillow-avif-plugin to compatible version range --- server/Dockerfile | 8 ++++---- server/requirements.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'server') diff --git a/server/Dockerfile b/server/Dockerfile index c2640f1..3e4dadf 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -23,15 +23,15 @@ RUN apk --no-cache add \ py3-pillow \ py3-pynacl \ py3-tz \ - py3-pyrfc3339 \ - && pip3 install --no-cache-dir --disable-pip-version-check \ + py3-pyrfc3339 +RUN pip3 install --no-cache-dir --disable-pip-version-check \ "alembic>=0.8.5" \ "coloredlogs==5.0" \ "pyheif==0.6.1" \ "heif-image-plugin>=0.3.2" \ yt-dlp \ - "pillow-avif-plugin>=1.1.0" \ - && apk --no-cache del py3-pip + "pillow-avif-plugin~=1.1.0" +RUN apk --no-cache del py3-pip COPY ./ /opt/app/ RUN rm -rf /opt/app/szurubooru/tests diff --git a/server/requirements.txt b/server/requirements.txt index ceff0b8..ffe18f0 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -3,7 +3,7 @@ certifi>=2017.11.5 coloredlogs==5.0 heif-image-plugin==0.3.2 numpy>=1.8.2 -pillow-avif-plugin>=1.1.0 +pillow-avif-plugin~=1.1.0 pillow>=4.3.0 psycopg2-binary>=2.6.1 pyheif==0.6.1 -- cgit v1.3 From b7218659314f1da81baa683d5d2c6fddb8c1b619 Mon Sep 17 00:00:00 2001 From: "Zak B. Elep" Date: Mon, 4 Nov 2024 00:56:11 +0800 Subject: server/config: generalize container support Allow running in Kubernetes, podman, and LXC, besides plain docker-compose, without having to fake out /.dockerenv in non-Docker environments. --- server/szurubooru/config.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/szurubooru/config.py b/server/szurubooru/config.py index 8f87642..f3f9007 100644 --- a/server/szurubooru/config.py +++ b/server/szurubooru/config.py @@ -21,7 +21,7 @@ def _merge(left: Dict, right: Dict) -> Dict: return left -def _docker_config() -> Dict: +def _container_config() -> Dict: if "TEST_ENVIRONMENT" not in os.environ: for key in ["POSTGRES_USER", "POSTGRES_PASSWORD", "POSTGRES_HOST"]: if key not in os.environ: @@ -49,6 +49,15 @@ def _file_config(filename: str) -> Dict: return yaml.load(handle.read(), Loader=yaml.SafeLoader) or {} +def _running_inside_container() -> bool: + env = os.environ.keys() + return ( + os.path.exists("/.dockerenv") + or "KUBERNETES_SERVICE_HOST" in env + or "container" in env # set by lxc/podman + ) + + def _read_config() -> Dict: ret = _file_config("config.yaml.dist") if os.path.isfile("config.yaml"): @@ -57,8 +66,8 @@ def _read_config() -> Dict: logger.warning( "'config.yaml' should be a file, not a directory, skipping" ) - if os.path.exists("/.dockerenv"): - ret = _merge(ret, _docker_config()) + if _running_inside_container(): + ret = _merge(ret, _container_config()) return ret -- cgit v1.3