Skip to content

Commit 4e3664b

Browse files
Move Transmission services into separate module (home-assistant#155490)
Co-authored-by: Copilot <[email protected]>
1 parent 76f5cc3 commit 4e3664b

File tree

2 files changed

+171
-156
lines changed

2 files changed

+171
-156
lines changed

homeassistant/components/transmission/__init__.py

Lines changed: 6 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@
1313
TransmissionConnectError,
1414
TransmissionError,
1515
)
16-
import voluptuous as vol
1716

18-
from homeassistant.config_entries import ConfigEntryState
1917
from homeassistant.const import (
2018
CONF_HOST,
21-
CONF_ID,
2219
CONF_NAME,
2320
CONF_PASSWORD,
2421
CONF_PATH,
@@ -27,35 +24,15 @@
2724
CONF_USERNAME,
2825
Platform,
2926
)
30-
from homeassistant.core import HomeAssistant, ServiceCall, callback
31-
from homeassistant.exceptions import (
32-
ConfigEntryAuthFailed,
33-
ConfigEntryNotReady,
34-
HomeAssistantError,
35-
)
36-
from homeassistant.helpers import (
37-
config_validation as cv,
38-
entity_registry as er,
39-
selector,
40-
)
27+
from homeassistant.core import HomeAssistant, callback
28+
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
29+
from homeassistant.helpers import config_validation as cv, entity_registry as er
4130
from homeassistant.helpers.typing import ConfigType
4231

43-
from .const import (
44-
ATTR_DELETE_DATA,
45-
ATTR_DOWNLOAD_PATH,
46-
ATTR_TORRENT,
47-
CONF_ENTRY_ID,
48-
DEFAULT_DELETE_DATA,
49-
DEFAULT_PATH,
50-
DEFAULT_SSL,
51-
DOMAIN,
52-
SERVICE_ADD_TORRENT,
53-
SERVICE_REMOVE_TORRENT,
54-
SERVICE_START_TORRENT,
55-
SERVICE_STOP_TORRENT,
56-
)
32+
from .const import DEFAULT_PATH, DEFAULT_SSL, DOMAIN
5733
from .coordinator import TransmissionConfigEntry, TransmissionDataUpdateCoordinator
5834
from .errors import AuthenticationError, CannotConnect, UnknownError
35+
from .services import async_setup_services
5936

6037
_LOGGER = logging.getLogger(__name__)
6138

@@ -76,51 +53,13 @@
7653
"Turtle Mode": "turtle_mode",
7754
}
7855

79-
SERVICE_BASE_SCHEMA = vol.Schema(
80-
{
81-
vol.Required(CONF_ENTRY_ID): selector.ConfigEntrySelector(
82-
{"integration": DOMAIN}
83-
),
84-
}
85-
)
86-
87-
SERVICE_ADD_TORRENT_SCHEMA = vol.All(
88-
SERVICE_BASE_SCHEMA.extend(
89-
{
90-
vol.Required(ATTR_TORRENT): cv.string,
91-
vol.Optional(ATTR_DOWNLOAD_PATH): cv.string,
92-
}
93-
),
94-
)
95-
96-
97-
SERVICE_REMOVE_TORRENT_SCHEMA = vol.All(
98-
SERVICE_BASE_SCHEMA.extend(
99-
{
100-
vol.Required(CONF_ID): cv.positive_int,
101-
vol.Optional(ATTR_DELETE_DATA, default=DEFAULT_DELETE_DATA): cv.boolean,
102-
}
103-
)
104-
)
105-
106-
SERVICE_START_TORRENT_SCHEMA = vol.All(
107-
SERVICE_BASE_SCHEMA.extend({vol.Required(CONF_ID): cv.positive_int}),
108-
)
109-
110-
SERVICE_STOP_TORRENT_SCHEMA = vol.All(
111-
SERVICE_BASE_SCHEMA.extend(
112-
{
113-
vol.Required(CONF_ID): cv.positive_int,
114-
}
115-
)
116-
)
11756

11857
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
11958

12059

12160
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
12261
"""Set up the Transmission component."""
123-
setup_hass_services(hass)
62+
async_setup_services(hass)
12463
return True
12564

12665

@@ -203,95 +142,6 @@ async def async_migrate_entry(
203142
return True
204143

205144

206-
def _get_coordinator_from_service_data(
207-
hass: HomeAssistant, entry_id: str
208-
) -> TransmissionDataUpdateCoordinator:
209-
"""Return coordinator for entry id."""
210-
entry: TransmissionConfigEntry | None = hass.config_entries.async_get_entry(
211-
entry_id
212-
)
213-
if entry is None or entry.state is not ConfigEntryState.LOADED:
214-
raise HomeAssistantError(f"Config entry {entry_id} is not found or not loaded")
215-
return entry.runtime_data
216-
217-
218-
def setup_hass_services(hass: HomeAssistant) -> None:
219-
"""Home Assistant services."""
220-
221-
async def add_torrent(service: ServiceCall) -> None:
222-
"""Add new torrent to download."""
223-
entry_id: str = service.data[CONF_ENTRY_ID]
224-
coordinator = _get_coordinator_from_service_data(hass, entry_id)
225-
torrent: str = service.data[ATTR_TORRENT]
226-
download_path: str | None = service.data.get(ATTR_DOWNLOAD_PATH)
227-
if torrent.startswith(
228-
("http", "ftp:", "magnet:")
229-
) or hass.config.is_allowed_path(torrent):
230-
if download_path:
231-
await hass.async_add_executor_job(
232-
partial(
233-
coordinator.api.add_torrent, torrent, download_dir=download_path
234-
)
235-
)
236-
else:
237-
await hass.async_add_executor_job(coordinator.api.add_torrent, torrent)
238-
await coordinator.async_request_refresh()
239-
else:
240-
_LOGGER.warning("Could not add torrent: unsupported type or no permission")
241-
242-
async def start_torrent(service: ServiceCall) -> None:
243-
"""Start torrent."""
244-
entry_id: str = service.data[CONF_ENTRY_ID]
245-
coordinator = _get_coordinator_from_service_data(hass, entry_id)
246-
torrent_id = service.data[CONF_ID]
247-
await hass.async_add_executor_job(coordinator.api.start_torrent, torrent_id)
248-
await coordinator.async_request_refresh()
249-
250-
async def stop_torrent(service: ServiceCall) -> None:
251-
"""Stop torrent."""
252-
entry_id: str = service.data[CONF_ENTRY_ID]
253-
coordinator = _get_coordinator_from_service_data(hass, entry_id)
254-
torrent_id = service.data[CONF_ID]
255-
await hass.async_add_executor_job(coordinator.api.stop_torrent, torrent_id)
256-
await coordinator.async_request_refresh()
257-
258-
async def remove_torrent(service: ServiceCall) -> None:
259-
"""Remove torrent."""
260-
entry_id: str = service.data[CONF_ENTRY_ID]
261-
coordinator = _get_coordinator_from_service_data(hass, entry_id)
262-
torrent_id = service.data[CONF_ID]
263-
delete_data = service.data[ATTR_DELETE_DATA]
264-
await hass.async_add_executor_job(
265-
partial(coordinator.api.remove_torrent, torrent_id, delete_data=delete_data)
266-
)
267-
await coordinator.async_request_refresh()
268-
269-
hass.services.async_register(
270-
DOMAIN, SERVICE_ADD_TORRENT, add_torrent, schema=SERVICE_ADD_TORRENT_SCHEMA
271-
)
272-
273-
hass.services.async_register(
274-
DOMAIN,
275-
SERVICE_REMOVE_TORRENT,
276-
remove_torrent,
277-
schema=SERVICE_REMOVE_TORRENT_SCHEMA,
278-
)
279-
280-
hass.services.async_register(
281-
DOMAIN,
282-
SERVICE_START_TORRENT,
283-
start_torrent,
284-
schema=SERVICE_START_TORRENT_SCHEMA,
285-
)
286-
287-
hass.services.async_register(
288-
DOMAIN,
289-
SERVICE_STOP_TORRENT,
290-
stop_torrent,
291-
schema=SERVICE_STOP_TORRENT_SCHEMA,
292-
)
293-
294-
295145
async def get_api(
296146
hass: HomeAssistant, entry: dict[str, Any]
297147
) -> transmission_rpc.Client:
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""Define services for the Transmission integration."""
2+
3+
from functools import partial
4+
import logging
5+
6+
import voluptuous as vol
7+
8+
from homeassistant.config_entries import ConfigEntryState
9+
from homeassistant.const import CONF_ID
10+
from homeassistant.core import HomeAssistant, ServiceCall, callback
11+
from homeassistant.exceptions import HomeAssistantError
12+
from homeassistant.helpers import config_validation as cv, selector
13+
14+
from .const import (
15+
ATTR_DELETE_DATA,
16+
ATTR_DOWNLOAD_PATH,
17+
ATTR_TORRENT,
18+
CONF_ENTRY_ID,
19+
DEFAULT_DELETE_DATA,
20+
DOMAIN,
21+
SERVICE_ADD_TORRENT,
22+
SERVICE_REMOVE_TORRENT,
23+
SERVICE_START_TORRENT,
24+
SERVICE_STOP_TORRENT,
25+
)
26+
from .coordinator import TransmissionConfigEntry, TransmissionDataUpdateCoordinator
27+
28+
_LOGGER = logging.getLogger(__name__)
29+
30+
SERVICE_BASE_SCHEMA = vol.Schema(
31+
{
32+
vol.Required(CONF_ENTRY_ID): selector.ConfigEntrySelector(
33+
{"integration": DOMAIN}
34+
),
35+
}
36+
)
37+
38+
SERVICE_ADD_TORRENT_SCHEMA = vol.All(
39+
SERVICE_BASE_SCHEMA.extend(
40+
{
41+
vol.Required(ATTR_TORRENT): cv.string,
42+
vol.Optional(ATTR_DOWNLOAD_PATH): cv.string,
43+
}
44+
),
45+
)
46+
47+
SERVICE_REMOVE_TORRENT_SCHEMA = vol.All(
48+
SERVICE_BASE_SCHEMA.extend(
49+
{
50+
vol.Required(CONF_ID): cv.positive_int,
51+
vol.Optional(ATTR_DELETE_DATA, default=DEFAULT_DELETE_DATA): cv.boolean,
52+
}
53+
)
54+
)
55+
56+
SERVICE_START_TORRENT_SCHEMA = vol.All(
57+
SERVICE_BASE_SCHEMA.extend({vol.Required(CONF_ID): cv.positive_int}),
58+
)
59+
60+
SERVICE_STOP_TORRENT_SCHEMA = vol.All(
61+
SERVICE_BASE_SCHEMA.extend(
62+
{
63+
vol.Required(CONF_ID): cv.positive_int,
64+
}
65+
)
66+
)
67+
68+
69+
def _get_coordinator_from_service_data(
70+
hass: HomeAssistant, entry_id: str
71+
) -> TransmissionDataUpdateCoordinator:
72+
"""Return coordinator for entry id."""
73+
entry: TransmissionConfigEntry | None = hass.config_entries.async_get_entry(
74+
entry_id
75+
)
76+
if entry is None or entry.state is not ConfigEntryState.LOADED:
77+
raise HomeAssistantError(f"Config entry {entry_id} is not found or not loaded")
78+
return entry.runtime_data
79+
80+
81+
async def _async_add_torrent(service: ServiceCall) -> None:
82+
"""Add new torrent to download."""
83+
entry_id: str = service.data[CONF_ENTRY_ID]
84+
coordinator = _get_coordinator_from_service_data(service.hass, entry_id)
85+
torrent: str = service.data[ATTR_TORRENT]
86+
download_path: str | None = service.data.get(ATTR_DOWNLOAD_PATH)
87+
if torrent.startswith(
88+
("http", "ftp:", "magnet:")
89+
) or service.hass.config.is_allowed_path(torrent):
90+
if download_path:
91+
await service.hass.async_add_executor_job(
92+
partial(
93+
coordinator.api.add_torrent, torrent, download_dir=download_path
94+
)
95+
)
96+
else:
97+
await service.hass.async_add_executor_job(
98+
coordinator.api.add_torrent, torrent
99+
)
100+
await coordinator.async_request_refresh()
101+
else:
102+
_LOGGER.warning("Could not add torrent: unsupported type or no permission")
103+
104+
105+
async def _async_start_torrent(service: ServiceCall) -> None:
106+
"""Start torrent."""
107+
entry_id: str = service.data[CONF_ENTRY_ID]
108+
coordinator = _get_coordinator_from_service_data(service.hass, entry_id)
109+
torrent_id = service.data[CONF_ID]
110+
await service.hass.async_add_executor_job(coordinator.api.start_torrent, torrent_id)
111+
await coordinator.async_request_refresh()
112+
113+
114+
async def _async_stop_torrent(service: ServiceCall) -> None:
115+
"""Stop torrent."""
116+
entry_id: str = service.data[CONF_ENTRY_ID]
117+
coordinator = _get_coordinator_from_service_data(service.hass, entry_id)
118+
torrent_id = service.data[CONF_ID]
119+
await service.hass.async_add_executor_job(coordinator.api.stop_torrent, torrent_id)
120+
await coordinator.async_request_refresh()
121+
122+
123+
async def _async_remove_torrent(service: ServiceCall) -> None:
124+
"""Remove torrent."""
125+
entry_id: str = service.data[CONF_ENTRY_ID]
126+
coordinator = _get_coordinator_from_service_data(service.hass, entry_id)
127+
torrent_id = service.data[CONF_ID]
128+
delete_data = service.data[ATTR_DELETE_DATA]
129+
await service.hass.async_add_executor_job(
130+
partial(coordinator.api.remove_torrent, torrent_id, delete_data=delete_data)
131+
)
132+
await coordinator.async_request_refresh()
133+
134+
135+
@callback
136+
def async_setup_services(hass: HomeAssistant) -> None:
137+
"""Register services for the Transmission integration."""
138+
139+
hass.services.async_register(
140+
DOMAIN,
141+
SERVICE_ADD_TORRENT,
142+
_async_add_torrent,
143+
schema=SERVICE_ADD_TORRENT_SCHEMA,
144+
)
145+
146+
hass.services.async_register(
147+
DOMAIN,
148+
SERVICE_REMOVE_TORRENT,
149+
_async_remove_torrent,
150+
schema=SERVICE_REMOVE_TORRENT_SCHEMA,
151+
)
152+
153+
hass.services.async_register(
154+
DOMAIN,
155+
SERVICE_START_TORRENT,
156+
_async_start_torrent,
157+
schema=SERVICE_START_TORRENT_SCHEMA,
158+
)
159+
160+
hass.services.async_register(
161+
DOMAIN,
162+
SERVICE_STOP_TORRENT,
163+
_async_stop_torrent,
164+
schema=SERVICE_STOP_TORRENT_SCHEMA,
165+
)

0 commit comments

Comments
 (0)