Compare commits

..
6 Commits
Author SHA1 Message Date
LeoVasanko bb1659e076 Internal and external auth cache 5 minutes. 2026-08-14 03:47:03 +00:00
LeoVasanko c445061451 preview: restore strict 10s request deadline
The mediapreview error-handling refactor dropped the outer
asyncio.wait_for around preview generation. Pool-internal timeouts only
bound individual stages (idle-worker wait, worker request, OO HTTP
calls); queueing on top of them let requests run far past 10s and
eventually return 200 instead of 503. Wrap both generate paths in
wait_for(PREVIEW_TIMEOUT) again — cancellation propagates correctly:
the pool dispatcher drops cancelled futures, and the OnlyOffice manager
cancels orphaned conversion tasks.
2026-08-13 07:55:48 +00:00
LeoVasanko 43fd7df098 Bump mediapreview version 2026-08-13 07:49:09 +00:00
LeoVasanko 768ccd7739 startup box: show mediapreview version next to cista version
Version comes from importlib.metadata (the package has no version
constant), making it obvious at a glance which mediapreview build the
running server resolved.
2026-08-13 07:46:29 +00:00
LeoVasanko 321a86acf8 5 min cache for auth checks to reduce spam. 2026-08-13 07:23:22 +00:00
LeoVasanko b5f4f67813 oosetup: return exit code, not the secret
The mediapreview setup_docker() return value (the JWT secret) was passed
through to main() and sys.exit(), printing it a second time on stderr
with a failure exit code. The secret is already printed to stdout in the
finally block; return 0 on success.
2026-08-13 07:16:17 +00:00
6 changed files with 23 additions and 10 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
import os
import sys
from importlib.metadata import version as pkg_version
from pathlib import Path
from docopt import docopt
@@ -30,7 +31,7 @@ def create_startup_box(
*, folder, url, unix=None, dev=False, paskia_url=None, public=False
):
"""Create a framed startup box with server information."""
title = f"Cista {cista.__version__}"
title = f"Cista {cista.__version__} (mediapreview {pkg_version('mediapreview')})"
listen = unix or url
location = f"{folder} @ {listen}"
lines = [title, location]
+1 -1
View File
@@ -226,7 +226,7 @@ def hydrate_request_auth_context(request, *, source: str) -> None:
_AUTH_REALM = "cista"
_AUTH_CACHE_TTL = 10
_AUTH_CACHE_TTL = 300
_auth_cache: dict[str, tuple[float, config.User]] = {}
_WINDOWS_UA_HINTS = (
"windows",
+3 -2
View File
@@ -21,7 +21,7 @@ def configure() -> None:
)
def setup_docker(confdir: Path | None = None) -> str:
def setup_docker(confdir: Path | None = None) -> int:
"""Build and run the patched OnlyOffice Docker image (via mediapreview)."""
if confdir is not None:
os.environ["CISTA_HOME"] = confdir.as_posix()
@@ -38,10 +38,11 @@ def setup_docker(confdir: Path | None = None) -> str:
)
configure()
try:
return mediapreview.office.setup_docker()
mediapreview.office.setup_docker()
finally:
# Print regardless of build outcome: the secret is deterministic
# (derived from the config).
sys.stdout.write(
f"ONLYOFFICE_JWT_SECRET={os.environ['ONLYOFFICE_JWT_SECRET']}\n"
)
return 0
+15 -4
View File
@@ -18,7 +18,9 @@ from mediapreview.exceptions import (
PreviewError,
)
from mediapreview.formats import OFFICE_PREVIEW_SUFFIXES
from mediapreview.formats import expected_backend as _expected_preview_backend
from mediapreview.pool import (
PREVIEW_TIMEOUT,
generate_office_preview,
run_preview,
)
@@ -80,14 +82,23 @@ async def preview(req, path):
logger.debug(f"Preview cache hit: {rel}")
return raw(cached.body, headers=cached.headers)
# Generate preview
# Generate preview. The outer deadline is strict: pool internals have
# their own timeouts, but queueing (workers, the OnlyOffice semaphore)
# must not let a request exceed PREVIEW_TIMEOUT.
try:
if filepath.suffix.lower() in OFFICE_PREVIEW_SUFFIXES:
img, preview_resp = await generate_office_preview(
filepath, quality, maxsize, maxzoom
img, preview_resp = await asyncio.wait_for(
generate_office_preview(filepath, quality, maxsize, maxzoom),
timeout=PREVIEW_TIMEOUT,
)
else:
img, preview_resp = await run_preview(filepath, quality, maxsize, maxzoom)
img, preview_resp = await asyncio.wait_for(
run_preview(filepath, quality, maxsize, maxzoom),
timeout=PREVIEW_TIMEOUT,
)
except TimeoutError:
req.ctx.log_extra = f"{_expected_preview_backend(filepath)} timeout"
return empty(503)
except PreviewError as e:
# mediapreview is responsible for backend-specific diagnostics; cista only
# needs the backend name, a short access-log reason, and a response status.
+1 -1
View File
@@ -68,7 +68,7 @@ async def close_client():
# Keyed by (credential hash, validation URL) so that entries for different
# perms/renew flags coexist and all entries for a credential can be purged
# on logout.
_VALIDATE_CACHE_TTL = 10
_VALIDATE_CACHE_TTL = 300
_validate_cache: dict[tuple[str, str], tuple[float, dict]] = {}
+1 -1
View File
@@ -33,7 +33,7 @@ dependencies = [
"html5tagger>=1.3.0",
"httpx>=0.28.0",
"inotify>=0.2.12",
"mediapreview[standard]>=0.2.2",
"mediapreview[standard]>=0.2.3",
"msgspec>=0.19.0",
"natsort>=8.4.0",
"numpy>=2.3.2",