Add whiteboard frame mode (Nextcloud Whiteboard / Excalidraw over WebDAV)
Build and push server image / build-and-push (push) Failing after 1m10s
Build and push server image / build-and-push (push) Failing after 1m10s
New third mode alongside photos/calendar: fetches a .whiteboard file over plain WebDAV (Basic auth -- generic, not Nextcloud-specific) and renders it via a small Node.js sidecar using Excalidraw's own real export code (@excalidraw/utils + @resvg/resvg-js, no headless browser), since a .whiteboard file turns out to be Excalidraw scene JSON, not an image. The sidecar runs as a second process inside this same container (Dockerfile installs Node, start.sh backgrounds it before exec'ing uvicorn) rather than a separate docker-compose service -- lightweight, stateless, reachable only at 127.0.0.1 from the Python process, nothing worth independently scaling. The rendered PNG is treated exactly like a photo from there on -- composed/quantized through the existing image_pipeline (letterboxed, never cropped) rather than a second parallel rendering pipeline. WebDAV credentials support the common "it's actually the same Nextcloud account as my CalDAV" case (an explicit opt-in checkbox, not silently inferred) while still working with any WebDAV server generically. Frame-level source (URL + owning account) follows the same owner- controls-their-own-data permission split as calendar sources and the week view's task list: only the account owner can point a frame at it, anyone linked can clear it. Honest limitation: this environment has no Node.js/npm, so render-service/ is written carefully against each library's documented API (verified via the npm registry, including transitive dependency licenses after the CalDAV/AGPL surprise earlier this session) but has never actually been executed. First real docker build is the first true test -- see render-service/README.md.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
"""Plain authenticated WebDAV file fetch -- whiteboard frame mode's way
|
||||
of pulling one specific file (a Nextcloud Whiteboard .whiteboard, or any
|
||||
other WebDAV server's file, this isn't Nextcloud-specific) out of a
|
||||
user's account. Deliberately just "GET this URL with Basic auth", the
|
||||
same shape as calendar_feed.py's plain ICS fetch -- no discovery, no
|
||||
account-wide browsing, since the caller already has (or pastes) the
|
||||
exact file URL, unlike caldav_client.py's calendar-account discovery
|
||||
flow which exists because a CalDAV account can hold several calendars
|
||||
worth picking between.
|
||||
|
||||
Pure functions -- no ORM, no FastAPI Depends -- same testability
|
||||
philosophy as calendar_feed.py/caldav_client.py.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import httpx
|
||||
|
||||
HTTP_TIMEOUT_S = 15.0
|
||||
FETCH_MAX_BYTES = 10 * 1024 * 1024 # a whiteboard scene is KB, not MB -- sanity cap, not an expected size
|
||||
|
||||
|
||||
class WebDavError(Exception):
|
||||
"""Fetch failed -- network, auth, a missing file, or an oversized
|
||||
response. Raised loudly; callers decide what to do."""
|
||||
|
||||
|
||||
def fetch_file(url: str, username: str, password: str) -> bytes:
|
||||
"""The raw bytes of one WebDAV file, HTTP Basic auth. That's the
|
||||
whole protocol surface whiteboard mode needs -- Basic auth over
|
||||
plain HTTP GET is what WebDAV file access boils down to once you
|
||||
already have the exact URL, no PROPFIND/discovery involved."""
|
||||
try:
|
||||
with httpx.stream("GET", url, auth=(username, password), timeout=HTTP_TIMEOUT_S,
|
||||
follow_redirects=True) as resp:
|
||||
resp.raise_for_status()
|
||||
chunks = []
|
||||
total = 0
|
||||
for chunk in resp.iter_bytes():
|
||||
total += len(chunk)
|
||||
if total > FETCH_MAX_BYTES:
|
||||
raise WebDavError(f"File exceeds {FETCH_MAX_BYTES} bytes")
|
||||
chunks.append(chunk)
|
||||
return b"".join(chunks)
|
||||
except httpx.HTTPError as e:
|
||||
raise WebDavError(str(e)) from e
|
||||
Reference in New Issue
Block a user