"""State shared by all ninlight entries: the firmware catalog and one-time download keys.""" from __future__ import annotations import asyncio from dataclasses import dataclass, field from datetime import datetime from pathlib import Path from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval from homeassistant.util.hass_dict import HassKey from .const import CATALOG_SCAN_INTERVAL, DOMAIN, FIRMWARE_DIR_NAME, SIGNAL_CATALOG_UPDATED from .firmware import FirmwareCatalog from .http import NinlightFirmwareView, OtaRegistry def firmware_directory(hass: HomeAssistant) -> Path: return Path(hass.config.path(FIRMWARE_DIR_NAME)) @dataclass class NinlightShared: """One instance per Home Assistant.""" catalog: FirmwareCatalog registry: OtaRegistry scan_lock: asyncio.Lock = field(default_factory=asyncio.Lock) unsub_scan: CALLBACK_TYPE | None = None DATA_SHARED: HassKey[NinlightShared] = HassKey(DOMAIN) @callback def async_get_shared(hass: HomeAssistant) -> NinlightShared: """Create on first use and register the download view exactly once.""" shared = hass.data.get(DATA_SHARED) if shared is None: shared = NinlightShared(FirmwareCatalog(firmware_directory(hass)), OtaRegistry()) hass.data[DATA_SHARED] = shared hass.http.register_view(NinlightFirmwareView(hass, shared.registry)) return shared async def async_scan_catalog(hass: HomeAssistant, shared: NinlightShared) -> None: async with shared.scan_lock: await hass.async_add_executor_job(shared.catalog.scan) async_dispatcher_send(hass, SIGNAL_CATALOG_UPDATED) async def async_start_catalog(hass: HomeAssistant, shared: NinlightShared) -> None: if shared.unsub_scan is not None: return async def _scheduled_scan(_now: datetime) -> None: await async_scan_catalog(hass, shared) shared.unsub_scan = async_track_time_interval( hass, _scheduled_scan, CATALOG_SCAN_INTERVAL, name="ninlight firmware catalog", cancel_on_shutdown=True, ) await async_scan_catalog(hass, shared) @callback def async_stop_catalog(shared: NinlightShared) -> None: if shared.unsub_scan is not None: shared.unsub_scan() shared.unsub_scan = None