Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various bugfixes #18

Merged
merged 3 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions custom_components/duplicati/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
24 changes: 13 additions & 11 deletions custom_components/duplicati/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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."""
Expand Down
4 changes: 2 additions & 2 deletions custom_components/duplicati/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
),
Expand Down
134 changes: 58 additions & 76 deletions custom_components/duplicati/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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",
)

Expand All @@ -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,
Expand All @@ -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",
)