diff --git a/api/ovkiosk/routers/image.py b/api/ovkiosk/routers/image.py index 0cec0a9..5cda51b 100644 --- a/api/ovkiosk/routers/image.py +++ b/api/ovkiosk/routers/image.py @@ -1,8 +1,10 @@ import io import logging import re -from typing import Iterator +import time +from typing import Iterator, Optional +from async_lru import alru_cache from fastapi import APIRouter, Depends, HTTPException, status from fastapi.responses import StreamingResponse from PIL import Image @@ -66,6 +68,32 @@ async def find_images( return list(file_names) +async def get_ttl_hash(seconds: int = 20) -> int: + """ + Return the same value withing `seconds` time period + https://stackoverflow.com/a/55900800 + """ + return round(time.time() / seconds) + + +@alru_cache(maxsize=20) +async def get_image_by_name( + file_name: str, + ttl_hash: Optional[int] = None, +) -> io.BytesIO: + del ttl_hash + + file = DavFile(CLIENT.resource(f"img/{file_name}"), refresh=False) + print(f"Downloading {file_name}") + await file.download() + img = Image.open(io.BytesIO(file.bytes)).convert("RGB") + + img_buffer = io.BytesIO() + img.save(img_buffer, format='JPEG', quality=85) + + return img_buffer + + @router.get( "/get/{prefix}", responses={ @@ -85,6 +113,7 @@ async def find_images( async def get_image( prefix: str, file_names: Iterator[str] = Depends(find_file_names), + ttl_hash: int = Depends(get_ttl_hash), ) -> StreamingResponse: file_names = list(file_names) @@ -94,17 +123,11 @@ async def get_image( elif len(file_names) > 1: raise HTTPException(status_code=status.HTTP_409_CONFLICT) - file_name = file_names[0] - file = DavFile(CLIENT.resource(f"img/{file_name}"), refresh=False) - await file.download() - img = Image.open(io.BytesIO(file.bytes)).convert("RGB") - - img_buffer = io.BytesIO() - img.save(img_buffer, format='JPEG', quality=85) + img_buffer = await get_image_by_name(file_names[0], ttl_hash) img_buffer.seek(0) return StreamingResponse( - img_buffer, + content=img_buffer, media_type="image/jpeg", headers={ "Content-Disposition": f"filename={prefix}.jpg" diff --git a/api/poetry.lock b/api/poetry.lock index 8b7d05c..4444950 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -41,6 +41,14 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] +[[package]] +name = "async-lru" +version = "1.0.3" +description = "Simple lru_cache for asyncio" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "certifi" version = "2022.6.15" @@ -363,7 +371,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "b39654ea53ee894dd65fe2217ca58fea259c9fe8a9bbae26f6198c42304608e2" +content-hash = "04507c7951fd5d6a92767f82635087e15da2eff67f52e22e79b6e1db863e9ed4" [metadata.files] anyio = [ @@ -374,6 +382,10 @@ apscheduler = [ {file = "APScheduler-3.9.1-py2.py3-none-any.whl", hash = "sha256:ddc25a0ddd899de44d7f451f4375fb971887e65af51e41e5dcf681f59b8b2c9a"}, {file = "APScheduler-3.9.1.tar.gz", hash = "sha256:65e6574b6395498d371d045f2a8a7e4f7d50c6ad21ef7313d15b1c7cf20df1e3"}, ] +async-lru = [ + {file = "async-lru-1.0.3.tar.gz", hash = "sha256:c2cb9b2915eb14e6cf3e717154b40f715bf90e596d73623677affd0d1fbcd32a"}, + {file = "async_lru-1.0.3-py3-none-any.whl", hash = "sha256:ea692c303feb6211ff260d230dae1583636f13e05c9ae616eada77855b7f415c"}, +] certifi = [ {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, diff --git a/api/pyproject.toml b/api/pyproject.toml index 0d5e3f5..61a3f6c 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -13,6 +13,7 @@ webdavclient3 = "3.14.5" Markdown = "^3.4.1" APScheduler = "^3.9.1" Pillow = "^9.2.0" +async-lru = "^1.0.3" [tool.poetry.dev-dependencies] # pytest = "^5.2"