diff --git a/homeassistant/components/nextcloud/__init__.py b/homeassistant/components/nextcloud/__init__.py index b4080dd2a1a..5dffcbf9fba 100644 --- a/homeassistant/components/nextcloud/__init__.py +++ b/homeassistant/components/nextcloud/__init__.py @@ -13,10 +13,10 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, discovery -from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.typing import ConfigType from .const import DEFAULT_SCAN_INTERVAL, DOMAIN +from .coordinator import NextcloudDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -40,61 +40,28 @@ CONFIG_SCHEMA = vol.Schema( ) -def setup(hass: HomeAssistant, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Nextcloud integration.""" - # Fetch Nextcloud Monitor api data conf = config[DOMAIN] try: - ncm = NextcloudMonitor(conf[CONF_URL], conf[CONF_USERNAME], conf[CONF_PASSWORD]) + ncm = await hass.async_add_executor_job( + NextcloudMonitor, conf[CONF_URL], conf[CONF_USERNAME], conf[CONF_PASSWORD] + ) except NextcloudMonitorError: _LOGGER.error("Nextcloud setup failed - Check configuration") return False - hass.data[DOMAIN] = get_data_points(ncm.data) - hass.data[DOMAIN]["instance"] = conf[CONF_URL] + coordinator = NextcloudDataUpdateCoordinator( + hass, + ncm, + conf, + ) + hass.data[DOMAIN] = coordinator - def nextcloud_update(event_time): - """Update data from nextcloud api.""" - try: - ncm.update() - except NextcloudMonitorError: - _LOGGER.error("Nextcloud update failed") - return False - - hass.data[DOMAIN] = get_data_points(ncm.data) - hass.data[DOMAIN]["instance"] = conf[CONF_URL] - - # Update sensors on time interval - track_time_interval(hass, nextcloud_update, conf[CONF_SCAN_INTERVAL]) + await coordinator.async_config_entry_first_refresh() for platform in PLATFORMS: discovery.load_platform(hass, platform, DOMAIN, {}, config) return True - - -# Use recursion to create list of sensors & values based on nextcloud api data -def get_data_points(api_data, key_path="", leaf=False): - """Use Recursion to discover data-points and values. - - Get dictionary of data-points by recursing through dict returned by api until - the dictionary value does not contain another dictionary and use the - resulting path of dictionary keys and resulting value as the name/value - for the data-point. - - returns: dictionary of data-point/values - """ - result = {} - for key, value in api_data.items(): - if isinstance(value, dict): - if leaf: - key_path = f"{key}_" - if not leaf: - key_path += f"{key}_" - leaf = True - result.update(get_data_points(value, key_path, leaf)) - else: - result[f"{DOMAIN}_{key_path}{key}"] = value - leaf = False - return result diff --git a/homeassistant/components/nextcloud/binary_sensor.py b/homeassistant/components/nextcloud/binary_sensor.py index 6e0df919f90..52ddb660071 100644 --- a/homeassistant/components/nextcloud/binary_sensor.py +++ b/homeassistant/components/nextcloud/binary_sensor.py @@ -7,6 +7,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import DOMAIN +from .coordinator import NextcloudDataUpdateCoordinator from .entity import NextcloudEntity BINARY_SENSORS = ( @@ -26,11 +27,16 @@ def setup_platform( """Set up the Nextcloud sensors.""" if discovery_info is None: return - binary_sensors = [] - for name in hass.data[DOMAIN]: - if name in BINARY_SENSORS: - binary_sensors.append(NextcloudBinarySensor(name)) - add_entities(binary_sensors, True) + coordinator: NextcloudDataUpdateCoordinator = hass.data[DOMAIN] + + add_entities( + [ + NextcloudBinarySensor(coordinator, name) + for name in coordinator.data + if name in BINARY_SENSORS + ], + True, + ) class NextcloudBinarySensor(NextcloudEntity, BinarySensorEntity): @@ -39,4 +45,4 @@ class NextcloudBinarySensor(NextcloudEntity, BinarySensorEntity): @property def is_on(self) -> bool: """Return true if the binary sensor is on.""" - return self._state == "yes" + return self.coordinator.data.get(self.item) == "yes" diff --git a/homeassistant/components/nextcloud/coordinator.py b/homeassistant/components/nextcloud/coordinator.py new file mode 100644 index 00000000000..07dc76d41dd --- /dev/null +++ b/homeassistant/components/nextcloud/coordinator.py @@ -0,0 +1,73 @@ +"""Data update coordinator for the Nextcloud integration.""" + +import logging +from typing import Any + +from nextcloudmonitor import NextcloudMonitor, NextcloudMonitorError + +from homeassistant.const import CONF_SCAN_INTERVAL, CONF_URL +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DEFAULT_SCAN_INTERVAL, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class NextcloudDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): + """Nextcloud data update coordinator.""" + + def __init__( + self, hass: HomeAssistant, ncm: NextcloudMonitor, config: ConfigType + ) -> None: + """Initialize the Nextcloud coordinator.""" + self.config = config + self.ncm = ncm + self.url = config[CONF_URL] + + super().__init__( + hass, + _LOGGER, + name=self.url, + update_interval=config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL), + ) + + # Use recursion to create list of sensors & values based on nextcloud api data + def _get_data_points( + self, api_data: dict, key_path: str = "", leaf: bool = False + ) -> dict[str, Any]: + """Use Recursion to discover data-points and values. + + Get dictionary of data-points by recursing through dict returned by api until + the dictionary value does not contain another dictionary and use the + resulting path of dictionary keys and resulting value as the name/value + for the data-point. + + returns: dictionary of data-point/values + """ + result = {} + for key, value in api_data.items(): + if isinstance(value, dict): + if leaf: + key_path = f"{key}_" + if not leaf: + key_path += f"{key}_" + leaf = True + result.update(self._get_data_points(value, key_path, leaf)) + else: + result[f"{DOMAIN}_{key_path}{key}"] = value + leaf = False + return result + + async def _async_update_data(self) -> dict[str, Any]: + """Fetch all Nextcloud data.""" + + def _update_data() -> None: + try: + self.ncm.update() + except NextcloudMonitorError as ex: + raise UpdateFailed from ex + + await self.hass.async_add_executor_job(_update_data) + return self._get_data_points(self.ncm.data) diff --git a/homeassistant/components/nextcloud/entity.py b/homeassistant/components/nextcloud/entity.py index cb066e0fcf7..54976351dd2 100644 --- a/homeassistant/components/nextcloud/entity.py +++ b/homeassistant/components/nextcloud/entity.py @@ -1,26 +1,23 @@ """Base entity for the Nextcloud integration.""" -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.typing import StateType - -from .const import DOMAIN -class NextcloudEntity(Entity): +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .coordinator import NextcloudDataUpdateCoordinator + + +class NextcloudEntity(CoordinatorEntity[NextcloudDataUpdateCoordinator]): """Base Nextcloud entity.""" _attr_icon = "mdi:cloud" - def __init__(self, item: str) -> None: - """Initialize the Nextcloud entity.""" - self._attr_name = item + def __init__(self, coordinator: NextcloudDataUpdateCoordinator, item: str) -> None: + """Initialize the Nextcloud sensor.""" + super().__init__(coordinator) self.item = item - self._state: StateType = None + self._attr_name = item @property - def unique_id(self): + def unique_id(self) -> str: """Return the unique ID for this sensor.""" - return f"{self.hass.data[DOMAIN]['instance']}#{self.item}" - - def update(self) -> None: - """Update the sensor.""" - self._state = self.hass.data[DOMAIN][self.item] + return f"{self.coordinator.url}#{self.item}" diff --git a/homeassistant/components/nextcloud/sensor.py b/homeassistant/components/nextcloud/sensor.py index 91d4411b0cb..459f22d30eb 100644 --- a/homeassistant/components/nextcloud/sensor.py +++ b/homeassistant/components/nextcloud/sensor.py @@ -7,6 +7,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from .const import DOMAIN +from .coordinator import NextcloudDataUpdateCoordinator from .entity import NextcloudEntity SENSORS = ( @@ -65,11 +66,16 @@ def setup_platform( """Set up the Nextcloud sensors.""" if discovery_info is None: return - sensors = [] - for name in hass.data[DOMAIN]: - if name in SENSORS: - sensors.append(NextcloudSensor(name)) - add_entities(sensors, True) + coordinator: NextcloudDataUpdateCoordinator = hass.data[DOMAIN] + + add_entities( + [ + NextcloudSensor(coordinator, name) + for name in coordinator.data + if name in SENSORS + ], + True, + ) class NextcloudSensor(NextcloudEntity, SensorEntity): @@ -78,4 +84,4 @@ class NextcloudSensor(NextcloudEntity, SensorEntity): @property def native_value(self) -> StateType: """Return the state for this sensor.""" - return self._state + return self.coordinator.data.get(self.item)