diff --git a/custom_components/duplicati/__init__.py b/custom_components/duplicati/__init__.py index 2282fad..e0dbcf7 100644 --- a/custom_components/duplicati/__init__.py +++ b/custom_components/duplicati/__init__.py @@ -76,11 +76,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: "host": host, "backups": backups, } - # Forward the setup to your platforms, passing the coordinator to them - for platform in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, platform) - ) + # Forward setup to used platforms + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) # Set up custom services await async_setup_services(hass) except aiohttp.ClientConnectionError as e: diff --git a/custom_components/duplicati/coordinator.py b/custom_components/duplicati/coordinator.py index 9984b4f..97a42a0 100644 --- a/custom_components/duplicati/coordinator.py +++ b/custom_components/duplicati/coordinator.py @@ -4,10 +4,10 @@ from datetime import datetime, timedelta from homeassistant.core import HomeAssistant -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util import dt as dt_util -from .api import ApiResponseError, CannotConnect, DuplicatiBackendAPI, InvalidAuth +from .api import ApiResponseError, DuplicatiBackendAPI from .const import ( DOMAIN, METRIC_DURATION, @@ -45,24 +45,26 @@ def __init__( ) self.api = api self.backup_id = backup_id + self.last_exception_message = None async def _async_update_data(self): - """Fetch data from Duplicati API.""" + """Fetch and process data from Duplicati API.""" try: + _LOGGER.debug( + "Start fetching %s data for backup with ID '%s' of server '%s'", + self.name, + self.backup_id, + self.api.get_api_host(), + ) # Get backup info backup_info = await self.api.get_backup(self.backup_id) if "Error" in backup_info: raise ApiResponseError(backup_info["Error"]) # Process metrics for sensors and return sensor data return self._process_data(backup_info) - except CannotConnect as e: - _LOGGER.error("Failed to connect: %s", str(e)) - except InvalidAuth as e: - _LOGGER.error("Authentication failed: %s", str(e)) - except ApiResponseError as e: - _LOGGER.error("API response error: %s", str(e)) - except Exception: - _LOGGER.exception("Unexpected exception") + except Exception as e: # noqa: BLE001 + self.last_exception_message = str(e) + raise UpdateFailed(str(e)) from e def _process_data(self, data): """Process raw data into sensor values.""" diff --git a/custom_components/duplicati/sensor.py b/custom_components/duplicati/sensor.py index 737c366..a7b9169 100644 --- a/custom_components/duplicati/sensor.py +++ b/custom_components/duplicati/sensor.py @@ -33,7 +33,7 @@ key=METRIC_STATUS, icon="mdi:shield-check", device_class=SensorDeviceClass.ENUM, - state_class=SensorStateClass.MEASUREMENT, + state_class=None, native_unit_of_measurement=None, options=[STATUS_OK, STATUS_ERROR], translation_key=METRIC_STATUS, @@ -42,7 +42,7 @@ key=METRIC_EXECUTION, icon="mdi:calendar-clock", device_class=SensorDeviceClass.TIMESTAMP, - state_class=SensorStateClass.MEASUREMENT, + state_class=None, native_unit_of_measurement=None, translation_key=METRIC_EXECUTION, ), diff --git a/custom_components/duplicati/service.py b/custom_components/duplicati/service.py index 732f97b..99f93b1 100644 --- a/custom_components/duplicati/service.py +++ b/custom_components/duplicati/service.py @@ -4,7 +4,6 @@ import logging import urllib.parse -import aiohttp from homeassistant.components.persistent_notification import async_create from homeassistant.core import HomeAssistant, ServiceCall @@ -71,7 +70,7 @@ def __init__(self, hass: HomeAssistant, api: DuplicatiBackendAPI) -> None: self.api = api self.coordinators = {} - async def _wait_for_backup_completion(self, backup_id): + async def __wait_for_backup_completion(self, backup_id): """Wait for the backup process to complete and fire an event.""" while True: # Check the backup progress state @@ -98,8 +97,9 @@ async def _wait_for_backup_completion(self, backup_id): if resp_backup_id == backup_id and resp_phase == "Backup_Complete": break _LOGGER.debug( - "Backup process for backup with ID '%s' in progress: %s%%", + "Backup creation for backup with ID '%s' of server '%s' in progress: %s%%", backup_id, + self.api.get_api_host(), resp_progress, ) # Wait for 1 second before checking the backup progress state again @@ -127,28 +127,29 @@ def get_number_of_coordinators(self) -> int: async def async_create_backup(self, backup_id): """Service to start a backup.""" try: - _LOGGER.info("Backup process for backup with ID '%s' initiated", backup_id) + _LOGGER.info( + "Backup creation for backup with ID '%s' of server '%s' initiated", + backup_id, + self.api.get_api_host(), + ) + # Check if the backup ID is valid backup_id = str(backup_id) if backup_id not in self.coordinators: raise DuplicatiServiceException("Unknown backup ID provided") + # Start the backup process - _LOGGER.info( - "Calling the Duplicati backend API to start the backup process for backup with ID '%s'", - backup_id, - ) - resp = await self.api.create_backup(backup_id) - if resp is None: + response = await self.api.create_backup(backup_id) + # Check if response is valid + if response is None: raise ApiResponseError("No API response received") - - _LOGGER.debug("Backup creation response: %s", resp) - + _LOGGER.debug("Backup creation response: %s", response) # Check if the backup process has been started - if "Error" in resp: - raise ApiResponseError(resp["Error"]) - if "Status" not in resp: + if "Error" in response: + raise ApiResponseError(response["Error"]) + if "Status" not in response: raise ApiResponseError("No status received in API response") - if resp["Status"] != "OK": + if response["Status"] != "OK": raise ApiResponseError("Unable to start the backup process") # Fire an event to notify that the backup process has started self.hass.bus.async_fire( @@ -158,8 +159,16 @@ async def async_create_backup(self, backup_id): "backup_id": backup_id, }, ) + # Wait for the backup process to complete - await self._wait_for_backup_completion(backup_id) + await self.__wait_for_backup_completion(backup_id) + + # Handle successful backup creation + _LOGGER.info( + "Backup creation for backup with ID '%s' of server '%s' successfully finished", + backup_id, + self.api.get_api_host(), + ) # Fire an event to notify that the backup process has finished self.hass.bus.async_fire( BACKUP_COMPLETED, @@ -168,50 +177,15 @@ async def async_create_backup(self, backup_id): "backup_id": backup_id, }, ) - _LOGGER.info( - "Backup creation for backup with ID '%s' sucessfully finished", - backup_id, - ) # Refresh the sensor data for the backup await self.async_refresh_sensor_data(backup_id) - except DuplicatiServiceException as e: - _LOGGER.error( - "Backup creation for backup with ID '%s' failed: %s", backup_id, str(e) - ) - # Fire an event to notify that the backup process has failed - self.hass.bus.async_fire( - BACKUP_FAILED, - { - "host": self.api.get_api_host(), - "backup_id": backup_id, - }, - ) - async_create( - self.hass, - f"Backup creation for backup with ID '{backup_id!s}' failed: {e!s}", - title="Backup creation error", - ) - except aiohttp.ClientConnectionError as e: - _LOGGER.error( - "Backup creation for backup with ID '%s' failed: %s", backup_id, str(e) - ) - # Fire an event to notify that the backup process has failed - self.hass.bus.async_fire( - BACKUP_FAILED, - { - "host": self.api.get_api_host(), - "backup_id": backup_id, - }, - ) - async_create( - self.hass, - f"Backup creation for backup with ID '{backup_id!s}' failed: {e!s}", - title="Backup creation error", - ) - - except ApiResponseError as e: + except Exception as e: # noqa: BLE001 + # Handle failed backup creation _LOGGER.error( - "Backup creation for backup with ID '%s' failed: %s", backup_id, str(e) + "Backup creation for backup with ID '%s' of server '%s' failed: %s", + backup_id, + self.api.get_api_host(), + str(e), ) # Fire an event to notify that the backup process has failed self.hass.bus.async_fire( @@ -221,9 +195,10 @@ async def async_create_backup(self, backup_id): "backup_id": backup_id, }, ) + # Create a notification in the UI async_create( self.hass, - f"Backup creation for backup with ID '{backup_id!s}' failed: {e!s}", + f"Backup creation for backup with ID '{backup_id!s}' of server '{self.api.get_api_host()}' failed: {e!s}", title="Backup creation error", ) @@ -234,11 +209,27 @@ async def async_refresh_sensor_data(self, backup_id): backup_id = str(backup_id) if backup_id not in self.coordinators: raise DuplicatiServiceException("Unknown backup ID provided") + # Get the coordinator of the backup ID coordinator = self.coordinators[backup_id] + _LOGGER.debug( + "Initiate sensor data refresh for backup with ID '%s' of server '%s'", + backup_id, + self.api.get_api_host(), + ) # Refresh the data await coordinator.async_refresh() - _LOGGER.info("Sensor data successfully refreshed") + + # Check if the refresh was successful + if not coordinator.last_update_success: + raise DuplicatiServiceException(coordinator.last_exception_message) + + # Handle successful refresh + _LOGGER.info( + "Sensor data refresh for backup with ID '%s' of server '%s' successfully completed", + backup_id, + self.api.get_api_host(), + ) # Fire an event to notify that the sensors have been refreshed self.hass.bus.async_fire( SENSORS_REFRESHED, @@ -247,25 +238,16 @@ async def async_refresh_sensor_data(self, backup_id): "backup_id": backup_id, }, ) - except DuplicatiServiceException as e: + except Exception as e: # noqa: BLE001$ + # Handle failed refresh _LOGGER.error( - "Sensors of backup with ID '%s' could not be refreshed: %s", + "Sensor data refresh for backup with ID '%s' of server '%s' failed", backup_id, - str(e), - ) - async_create( - self.hass, - f"Sensors of backup with ID '{backup_id!s}' could not be refreshed: {e!s}", - title="Sensor refresh error", - ) - except Exception as e: # noqa: BLE001 - _LOGGER.error( - "Sensors of backup with ID '%s' could not be refreshed: %s", - backup_id, - str(e), + self.api.get_api_host(), ) + # Create a notification in the UI async_create( self.hass, - f"Sensors of backup with ID '{backup_id!s}' could not be refreshed: {e!s}", + f"Sensor data refresh for backup with ID '{backup_id!s}' of server '{self.api.get_api_host()}' failed: {str(e)!s}", title="Sensor refresh error", )