diff --git a/homeassistant/components/transmission/services.py b/homeassistant/components/transmission/services.py index 2de43584058ca8..ff03583e470f43 100644 --- a/homeassistant/components/transmission/services.py +++ b/homeassistant/components/transmission/services.py @@ -2,13 +2,14 @@ from functools import partial import logging +from typing import cast import voluptuous as vol from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant, ServiceCall, callback -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import config_validation as cv, selector from .const import ( @@ -23,7 +24,7 @@ SERVICE_START_TORRENT, SERVICE_STOP_TORRENT, ) -from .coordinator import TransmissionConfigEntry, TransmissionDataUpdateCoordinator +from .coordinator import TransmissionDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -67,45 +68,52 @@ def _get_coordinator_from_service_data( - hass: HomeAssistant, entry_id: str + call: ServiceCall, ) -> TransmissionDataUpdateCoordinator: """Return coordinator for entry id.""" - entry: TransmissionConfigEntry | None = hass.config_entries.async_get_entry( - entry_id - ) - if entry is None or entry.state is not ConfigEntryState.LOADED: - raise HomeAssistantError(f"Config entry {entry_id} is not found or not loaded") - return entry.runtime_data + config_entry_id: str = call.data[CONF_ENTRY_ID] + if not (entry := call.hass.config_entries.async_get_entry(config_entry_id)): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="integration_not_found", + translation_placeholders={"target": DOMAIN}, + ) + if entry.state is not ConfigEntryState.LOADED: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="not_loaded", + translation_placeholders={"target": entry.title}, + ) + return cast(TransmissionDataUpdateCoordinator, entry.runtime_data) async def _async_add_torrent(service: ServiceCall) -> None: """Add new torrent to download.""" - entry_id: str = service.data[CONF_ENTRY_ID] - coordinator = _get_coordinator_from_service_data(service.hass, entry_id) + coordinator = _get_coordinator_from_service_data(service) torrent: str = service.data[ATTR_TORRENT] download_path: str | None = service.data.get(ATTR_DOWNLOAD_PATH) - if torrent.startswith( - ("http", "ftp:", "magnet:") - ) or service.hass.config.is_allowed_path(torrent): - if download_path: - await service.hass.async_add_executor_job( - partial( - coordinator.api.add_torrent, torrent, download_dir=download_path - ) - ) - else: - await service.hass.async_add_executor_job( - coordinator.api.add_torrent, torrent - ) - await coordinator.async_request_refresh() + + if not ( + torrent.startswith(("http", "ftp:", "magnet:")) + or service.hass.config.is_allowed_path(torrent) + ): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="could_not_add_torrent", + ) + + if download_path: + await service.hass.async_add_executor_job( + partial(coordinator.api.add_torrent, torrent, download_dir=download_path) + ) else: - _LOGGER.warning("Could not add torrent: unsupported type or no permission") + await service.hass.async_add_executor_job(coordinator.api.add_torrent, torrent) + await coordinator.async_request_refresh() async def _async_start_torrent(service: ServiceCall) -> None: """Start torrent.""" - entry_id: str = service.data[CONF_ENTRY_ID] - coordinator = _get_coordinator_from_service_data(service.hass, entry_id) + coordinator = _get_coordinator_from_service_data(service) torrent_id = service.data[CONF_ID] await service.hass.async_add_executor_job(coordinator.api.start_torrent, torrent_id) await coordinator.async_request_refresh() @@ -113,8 +121,7 @@ async def _async_start_torrent(service: ServiceCall) -> None: async def _async_stop_torrent(service: ServiceCall) -> None: """Stop torrent.""" - entry_id: str = service.data[CONF_ENTRY_ID] - coordinator = _get_coordinator_from_service_data(service.hass, entry_id) + coordinator = _get_coordinator_from_service_data(service) torrent_id = service.data[CONF_ID] await service.hass.async_add_executor_job(coordinator.api.stop_torrent, torrent_id) await coordinator.async_request_refresh() @@ -122,8 +129,7 @@ async def _async_stop_torrent(service: ServiceCall) -> None: async def _async_remove_torrent(service: ServiceCall) -> None: """Remove torrent.""" - entry_id: str = service.data[CONF_ENTRY_ID] - coordinator = _get_coordinator_from_service_data(service.hass, entry_id) + coordinator = _get_coordinator_from_service_data(service) torrent_id = service.data[CONF_ID] delete_data = service.data[ATTR_DELETE_DATA] await service.hass.async_add_executor_job( diff --git a/homeassistant/components/transmission/services.yaml b/homeassistant/components/transmission/services.yaml index 8f9aadd500938e..cadfbee2f63830 100644 --- a/homeassistant/components/transmission/services.yaml +++ b/homeassistant/components/transmission/services.yaml @@ -1,6 +1,7 @@ add_torrent: fields: entry_id: + required: true selector: config_entry: integration: transmission @@ -18,6 +19,7 @@ add_torrent: remove_torrent: fields: entry_id: + required: true selector: config_entry: integration: transmission @@ -27,6 +29,7 @@ remove_torrent: selector: text: delete_data: + required: true default: false selector: boolean: @@ -34,10 +37,12 @@ remove_torrent: start_torrent: fields: entry_id: + required: true selector: config_entry: integration: transmission id: + required: true example: 123 selector: text: @@ -45,6 +50,7 @@ start_torrent: stop_torrent: fields: entry_id: + required: true selector: config_entry: integration: transmission diff --git a/homeassistant/components/transmission/strings.json b/homeassistant/components/transmission/strings.json index 988a1b49db9556..903f48885ea51c 100644 --- a/homeassistant/components/transmission/strings.json +++ b/homeassistant/components/transmission/strings.json @@ -87,6 +87,17 @@ } } }, + "exceptions": { + "could_not_add_torrent": { + "message": "Could not add torrent: unsupported type or no permission." + }, + "integration_not_found": { + "message": "Integration \"{target}\" not found in registry." + }, + "not_loaded": { + "message": "{target} is not loaded." + } + }, "options": { "step": { "init": {