Transmission Service validation and fixes (#155554)

This commit is contained in:
Andrew Jackson
2025-11-11 16:29:42 +00:00
committed by GitHub
parent 8e499569a4
commit d0ff617e17
3 changed files with 66 additions and 43 deletions

View File

@@ -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 @@ from .const import (
SERVICE_START_TORRENT,
SERVICE_STOP_TORRENT,
)
from .coordinator import TransmissionConfigEntry, TransmissionDataUpdateCoordinator
from .coordinator import TransmissionDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@@ -67,45 +68,52 @@ SERVICE_STOP_TORRENT_SCHEMA = vol.All(
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(

View File

@@ -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,17 +37,20 @@ remove_torrent:
start_torrent:
fields:
entry_id:
selector:
config_entry:
integration: transmission
id:
example: 123
selector:
text:
stop_torrent:
fields:
entry_id:
required: true
selector:
config_entry:
integration: transmission
id:
required: true
example: 123
selector:
text:
stop_torrent:
fields:
entry_id:
required: true
selector:
config_entry:
integration: transmission

View File

@@ -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": {