|
| 1 | +import asyncio |
1 | 2 | import base64 |
2 | 3 | import html as _html |
3 | 4 | import io |
@@ -255,7 +256,12 @@ async def export_page( |
255 | 256 | page = dict(rows[0]) |
256 | 257 | if await resolve_page_permission(db, user, page["id"]) == "none": |
257 | 258 | raise HTTPException(status_code=404, detail="Page not found") |
258 | | - html_content = _inline_media_srcs(md_to_simple_html(page["content_md"])) |
| 259 | + # Render + inline media in a thread: _inline_media_srcs reads files |
| 260 | + # synchronously and md_to_simple_html does CPU work. Pages with many |
| 261 | + # embedded images would otherwise stall the event loop. |
| 262 | + html_content = await asyncio.to_thread( |
| 263 | + lambda: _inline_media_srcs(md_to_simple_html(page["content_md"])) |
| 264 | + ) |
259 | 265 | # Escape title/slug: they're rendered into <title>, <h1>, and a meta |
260 | 266 | # breadcrumb as raw text, so a page title containing e.g. `<script>` would |
261 | 267 | # execute when the exported HTML is opened locally (file:// context). |
@@ -316,12 +322,17 @@ async def export_site( |
316 | 322 | "SELECT id, slug, title, content_md FROM pages WHERE deleted_at IS NULL ORDER BY title" |
317 | 323 | ) |
318 | 324 |
|
319 | | - buf = io.BytesIO() |
320 | | - with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf: |
321 | | - for filename, content in build_site_files(pages): |
322 | | - zf.writestr(filename, content) |
| 325 | + # File reads (_inline_media_srcs), HTML rendering, and zip compression |
| 326 | + # are all blocking; do the whole bundle off the event loop. |
| 327 | + def _build_zip() -> io.BytesIO: |
| 328 | + out = io.BytesIO() |
| 329 | + with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zf: |
| 330 | + for filename, content in build_site_files(pages): |
| 331 | + zf.writestr(filename, content) |
| 332 | + out.seek(0) |
| 333 | + return out |
323 | 334 |
|
324 | | - buf.seek(0) |
| 335 | + buf = await asyncio.to_thread(_build_zip) |
325 | 336 | return StreamingResponse( |
326 | 337 | buf, |
327 | 338 | media_type="application/zip", |
|
0 commit comments