Move preview error handling to mediapreview.

This commit is contained in:
2026-08-13 04:29:25 +00:00
parent 226f96c477
commit 0c9fe7638c
3 changed files with 20 additions and 73 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ def configure() -> None:
) )
def setup_docker(confdir: Path | None = None) -> int: def setup_docker(confdir: Path | None = None) -> str:
"""Build and run the patched OnlyOffice Docker image (via mediapreview).""" """Build and run the patched OnlyOffice Docker image (via mediapreview)."""
if confdir is not None: if confdir is not None:
os.environ["CISTA_HOME"] = confdir.as_posix() os.environ["CISTA_HOME"] = confdir.as_posix()
+18 -71
View File
@@ -6,22 +6,19 @@ Sanic with auth, etag negotiation and the in-memory response cache.
""" """
import asyncio import asyncio
import re
import urllib.parse import urllib.parse
from pathlib import PurePosixPath from pathlib import PurePosixPath
from urllib.parse import unquote from urllib.parse import unquote
from wsgiref.handlers import format_date_time from wsgiref.handlers import format_date_time
import httpx
from mediapreview import CachedPreview, PreviewCache, is_previewable_path from mediapreview import CachedPreview, PreviewCache, is_previewable_path
from mediapreview.formats import OFFICE_PREVIEW_SUFFIXES from mediapreview.exceptions import (
from mediapreview.formats import expected_backend as _expected_preview_backend PreviewBackendError,
from mediapreview.office import onlyoffice_error_short_text PreviewCancelledError,
from mediapreview.pool import (
PREVIEW_TIMEOUT,
PreviewError, PreviewError,
PreviewPoolClosedError, )
PreviewTimeoutError, from mediapreview.formats import OFFICE_PREVIEW_SUFFIXES
from mediapreview.pool import (
generate_office_preview, generate_office_preview,
run_preview, run_preview,
) )
@@ -39,21 +36,6 @@ bp = Blueprint("preview", url_prefix="/preview")
_preview_cache = PreviewCache(capacity=500) _preview_cache = PreviewCache(capacity=500)
def _shorten_error(detail: str) -> str:
"""Shorten an upstream backend error for the single-line access log.
Backend errors arrive verbatim from ffmpeg/pyvips/pymupdf and often carry
an '[Errno N]' prefix, the input file path, and multi-line library noise —
all redundant with the URL already in the log line. Keep the first line,
drop the bracket prefix, and cut at the first ': ' separator.
"""
lines = detail.splitlines()
if not lines:
return ""
first_line = re.sub(r"^\[[^\]]*\]\s*", "", lines[0].strip())
return first_line.split(": ", 1)[0]
@bp.on_request @bp.on_request
async def verify_preview(request): async def verify_preview(request):
"""Verify access to preview routes.""" """Verify access to preview routes."""
@@ -101,57 +83,22 @@ async def preview(req, path):
# Generate preview # Generate preview
try: try:
if filepath.suffix.lower() in OFFICE_PREVIEW_SUFFIXES: if filepath.suffix.lower() in OFFICE_PREVIEW_SUFFIXES:
img, preview_resp = await asyncio.wait_for( img, preview_resp = await generate_office_preview(
generate_office_preview(filepath, quality, maxsize, maxzoom), filepath, quality, maxsize, maxzoom
timeout=PREVIEW_TIMEOUT,
) )
else: else:
img, preview_resp = await asyncio.wait_for( img, preview_resp = await run_preview(filepath, quality, maxsize, maxzoom)
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 PreviewTimeoutError as e:
req.ctx.log_extra = (
f"{(e.backend or _expected_preview_backend(filepath))} timeout"
)
return empty(503)
except httpx.HTTPStatusError:
req.ctx.log_extra = "onlyoffice N/A"
return empty(503)
except httpx.RequestError:
req.ctx.log_extra = "onlyoffice N/A"
return empty(503)
except RuntimeError as e:
detail = str(e)
if detail.startswith("OnlyOffice"):
req.ctx.log_extra = onlyoffice_error_short_text(detail)
return empty(503)
raise
except PreviewPoolClosedError:
# Server is shutting down; not an error, just a cancelled preview.
req.ctx.log_extra = "preview cancelled"
return empty(503)
except PreviewError as e: except PreviewError as e:
detail = str(e) # mediapreview is responsible for backend-specific diagnostics; cista only
if detail == "preview worker error" and e.stderr: # needs the backend name, a short access-log reason, and a response status.
captured = e.stderr.strip() if isinstance(e, PreviewCancelledError):
if captured: req.ctx.log_extra = e.short or "preview cancelled"
detail = captured.splitlines()[0] raise asyncio.CancelledError from e
# The worker already logged the failure (with traceback where the status = 422 if isinstance(e, PreviewBackendError) else 503
# error occurred) — annotate the access log instead of re-logging, req.ctx.log_extra = f"{e.backend}: {e.short}" if e.backend else e.short
# with a shortened reason. In dev mode, print the full error too.
backend = e.backend or _expected_preview_backend(filepath)
if req.app.debug: if req.app.debug:
full = detail logger.warning("%s", str(e))
if e.stderr and e.stderr.strip() not in detail: return empty(status)
full = f"{detail}\n{e.stderr.strip()}"
logger.warning("[%s] preview failed: %s", backend, full.strip())
short = _shorten_error(detail)
req.ctx.log_extra = f"{backend}: {short}" if short else backend
return empty(422)
except asyncio.CancelledError: except asyncio.CancelledError:
# Server shutdown or client disconnect: the connection is being torn # Server shutdown or client disconnect: the connection is being torn
# down, so responding is impossible — just annotate the access log. # down, so responding is impossible — just annotate the access log.
+1 -1
View File
@@ -33,7 +33,7 @@ dependencies = [
"html5tagger>=1.3.0", "html5tagger>=1.3.0",
"httpx>=0.28.0", "httpx>=0.28.0",
"inotify>=0.2.12", "inotify>=0.2.12",
"mediapreview[standard]", "mediapreview[standard]>=0.2.0",
"msgspec>=0.19.0", "msgspec>=0.19.0",
"natsort>=8.4.0", "natsort>=8.4.0",
"numpy>=2.3.2", "numpy>=2.3.2",