from __future__ import annotations import asyncio import json import logging import os from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from .const import DOMAIN, SERVICE_NEXT_SLIDE, SERVICE_REFRESH_ALBUM, ATTR_ENTRY_ID from .store import SlideshowStore _LOGGER = logging.getLogger(__name__) PLATFORMS: list[str] = ["camera", "sensor", "button", "number", "select", "text", "switch"] CARD_STATIC_PATH = "/album_slideshow_static" CARD_FILE = "album-slideshow-card.js" async def _async_register_card(hass: HomeAssistant) -> None: """Serve the Lovelace card JS and register it as a frontend module. Idempotent: only the first config entry to load triggers registration for the HA session. The card lets dashboards cross-fade between slides in the browser (GPU compositor) instead of forcing the camera entity to render a JPEG burst on the event loop. """ if hass.data.get(DOMAIN, {}).get("card_registered"): return integration_dir = os.path.dirname(__file__) www_dir = os.path.join(integration_dir, "www") card_path = os.path.join(www_dir, CARD_FILE) if not os.path.isfile(card_path): # Some HACS upgrade paths (and broken zip extractors) drop the # ``www/`` subdirectory. Try to recover by checking whether the # integration root has the file under a literal-backslash name # (a symptom of zips written with Windows path separators) or # directly at the root, and salvage it into ``www/`` so the # rest of the registration can proceed. recovered = await hass.async_add_executor_job( _recover_card_from_root, integration_dir, www_dir, card_path ) if not recovered: _LOGGER.warning( "Album Slideshow card missing on disk (%s). Re-install" " the integration via HACS (3-dot menu -> Redownload)" " or copy %s/%s into the album_slideshow folder." " The custom:album-slideshow-card type will not be" " available until this is fixed.", card_path, "www", CARD_FILE, ) return try: from homeassistant.components.http import StaticPathConfig await hass.http.async_register_static_paths( [StaticPathConfig(CARD_STATIC_PATH, www_dir, False)] ) except Exception: # noqa: BLE001 - many possible failure modes here _LOGGER.exception( "Failed to register static path for Album Slideshow card" ) return # Cache-bust the card URL with the integration version so dashboards # always pick up the script that matches the running integration # rather than a stale copy from a previous release. version = await hass.async_add_executor_job( _read_manifest_version, integration_dir ) card_url = f"{CARD_STATIC_PATH}/{CARD_FILE}" if version: card_url = f"{card_url}?v={version}" # Prefer registering the card as a Lovelace resource for storage-mode # dashboards. Resources are loaded as part of the Lovelace bootstrap, # before any dashboard renders custom cards, which removes the race # where the dashboard can hit "Custom element doesn't exist" if the # browser hasn't finished loading the module yet (a real risk after # HA restarts and integration upgrades that bust the cache). # # The storage-mode resources collection is only consumed by # storage-mode dashboards; YAML-mode dashboards (whether the user # has ``lovelace.mode: yaml`` globally or per-dashboard # ``mode: yaml`` entries) read only the resources declared in their # own YAML and would otherwise never load the card. That's why we # *always* also call ``add_extra_js_url``: the frontend injects a # ``