fix v1.7.4: PDF upload via HTTP endpoint instead of WebSocket — root cause was WS message-size limit dropping the connection on real (>2-3MB) PDFs (which also closed the dialog via reconnect). New POST /api/houseplan/upload (HomeAssistantView, multipart, auth); verified 3MB(182ms)+10MB(446ms). Legible errors (_errText), no more [object Object]

This commit is contained in:
JB
2026-07-04 16:10:26 +03:00
parent dea0faf268
commit 96d3a182c6
11 changed files with 163 additions and 43 deletions
+4 -1
View File
@@ -27,11 +27,14 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config) -> bool:
"""Регистрируем WS-команды и хранилища на старте."""
"""Регистрируем WS-команды, HTTP-загрузку и хранилища на старте."""
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN]["store"] = Store(hass, STORAGE_VERSION, STORAGE_KEY)
hass.data[DOMAIN]["config_store"] = Store(hass, STORAGE_VERSION, STORAGE_CONFIG_KEY)
hp_ws.async_register(hass)
from .http_api import HouseplanUploadView
hass.http.register_view(HouseplanUploadView())
return True
+1 -1
View File
@@ -10,7 +10,7 @@ PLANS_DIR = "houseplan/plans" # относительно каталога ко
FILES_URL = "/houseplan_files/files"
FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.7.3"
VERSION = "1.7.4"
DEFAULT_CONFIG: dict = {
"spaces": [],
File diff suppressed because one or more lines are too long
+83
View File
@@ -0,0 +1,83 @@
"""HTTP-эндпоинт загрузки файлов-инструкций House Plan.
Файлы (PDF и т.п.) грузятся не через WebSocket (у него лимит размера сообщения —
большой PDF рвёт соединение), а обычным multipart POST — как медиа в самом HA.
"""
from __future__ import annotations
import logging
from pathlib import Path
from aiohttp import web
from homeassistant.components.http import HomeAssistantView
from homeassistant.core import HomeAssistant
from .const import CONF_ADMIN_ONLY, DOMAIN, FILES_DIR, FILES_URL
from .validation import (
FILE_EXTENSIONS,
MAX_FILE_BYTES,
file_ext,
sanitize_filename,
sanitize_marker_id,
)
_LOGGER = logging.getLogger(__name__)
class HouseplanUploadView(HomeAssistantView):
"""POST /api/houseplan/upload — сохранить файл маркера, вернуть URL."""
url = "/api/houseplan/upload"
name = "api:houseplan:upload"
requires_auth = True
async def post(self, request: web.Request) -> web.Response:
hass: HomeAssistant = request.app["hass"]
entry = hass.data.get(DOMAIN, {}).get("entry")
admin_only = bool(entry and entry.options.get(CONF_ADMIN_ONLY, False))
if admin_only:
user = request.get("hass_user")
if user is None or not user.is_admin:
return web.json_response({"error": "unauthorized"}, status=403)
marker_id = "misc"
filename: str | None = None
blob: bytes | None = None
try:
reader = await request.multipart()
async for part in reader:
if part.name == "marker_id":
marker_id = sanitize_marker_id(await part.text())
elif part.name == "file":
filename = part.filename or "file"
blob = await part.read(decode=False)
except Exception as err: # noqa: BLE001
_LOGGER.warning("House Plan upload: ошибка чтения multipart: %s", err)
return web.json_response({"error": "bad_request"}, status=400)
if blob is None or not filename:
return web.json_response({"error": "no_file"}, status=400)
ext = file_ext(filename)
if ext not in FILE_EXTENSIONS:
return web.json_response(
{"error": "bad_ext", "allowed": sorted(FILE_EXTENSIONS)}, status=400
)
if len(blob) > MAX_FILE_BYTES:
return web.json_response(
{"error": "too_large", "max_mb": MAX_FILE_BYTES // 1024 // 1024}, status=413
)
safe_name = sanitize_filename(filename)
target_dir = Path(hass.config.path(FILES_DIR)) / marker_id
path = target_dir / safe_name
def _write() -> None:
target_dir.mkdir(parents=True, exist_ok=True)
path.write_bytes(blob)
await hass.async_add_executor_job(_write)
mtime = int(path.stat().st_mtime)
return web.json_response(
{"ok": True, "url": f"{FILES_URL}/{marker_id}/{safe_name}?v={mtime}", "name": filename}
)
+1 -1
View File
@@ -15,5 +15,5 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/justbusiness/houseplan-card/issues",
"requirements": [],
"version": "1.7.3"
"version": "1.7.4"
}