From a5960830d79d64d6f0d3890a71e5f78c70125a13 Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Tue, 26 Nov 2019 21:22:12 +0400 Subject: [PATCH] Add host field to add_torrent service (#28653) * Add host field to add_torrent service * Code cleanup * use name instead of host for service * update add_torrent --- .../transmission/.translations/en.json | 40 ++++++-------- .../components/transmission/__init__.py | 52 +++++++++++-------- .../components/transmission/config_flow.py | 24 ++++----- .../components/transmission/sensor.py | 11 +++- .../components/transmission/services.yaml | 3 ++ .../components/transmission/switch.py | 11 +++- .../transmission/test_config_flow.py | 2 - 7 files changed, 79 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/transmission/.translations/en.json b/homeassistant/components/transmission/.translations/en.json index aa8b99a4914..45c16be36e2 100644 --- a/homeassistant/components/transmission/.translations/en.json +++ b/homeassistant/components/transmission/.translations/en.json @@ -1,42 +1,34 @@ { "config": { - "abort": { - "already_configured": "Host is already configured.", - "one_instance_allowed": "Only a single instance is necessary." - }, - "error": { - "cannot_connect": "Unable to Connect to host", - "name_exists": "Name already exists", - "wrong_credentials": "Wrong username or password" - }, + "title": "Transmission", "step": { - "options": { - "data": { - "scan_interval": "Update frequency" - }, - "title": "Configure Options" - }, "user": { + "title": "Setup Transmission Client", "data": { - "host": "Host", "name": "Name", + "host": "Host", + "username": "Username", "password": "Password", - "port": "Port", - "username": "Username" - }, - "title": "Setup Transmission Client" + "port": "Port" + } } }, - "title": "Transmission" + "error": { + "name_exists": "Name already exists", + "wrong_credentials": "Wrong username or password", + "cannot_connect": "Unable to Connect to host" + }, + "abort": { + "already_configured": "Host is already configured." + } }, "options": { "step": { "init": { + "title": "Configure options for Transmission", "data": { "scan_interval": "Update frequency" - }, - "description": "Configure options for Transmission", - "title": "Configure options for Transmission" + } } } } diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index be41ca85998..7bbc61a192f 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -19,7 +19,6 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.event import async_track_time_interval -from homeassistant.util import slugify from .const import ( ATTR_TORRENT, @@ -28,13 +27,16 @@ from .const import ( DEFAULT_SCAN_INTERVAL, DOMAIN, SERVICE_ADD_TORRENT, + DATA_UPDATED, ) from .errors import AuthenticationError, CannotConnect, UnknownError _LOGGER = logging.getLogger(__name__) -SERVICE_ADD_TORRENT_SCHEMA = vol.Schema({vol.Required(ATTR_TORRENT): cv.string}) +SERVICE_ADD_TORRENT_SCHEMA = vol.Schema( + {vol.Required(ATTR_TORRENT): cv.string, vol.Required(CONF_NAME): cv.string} +) TRANS_SCHEMA = vol.All( vol.Schema( @@ -55,6 +57,8 @@ CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.All(cv.ensure_list, [TRANS_SCHEMA])}, extra=vol.ALLOW_EXTRA ) +PLATFORMS = ["sensor", "switch"] + async def async_setup(hass, config): """Import the Transmission Component from config.""" @@ -82,15 +86,15 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload Transmission Entry from config_entry.""" - client = hass.data[DOMAIN][config_entry.entry_id] - hass.services.async_remove(DOMAIN, client.service_name) + client = hass.data[DOMAIN].pop(config_entry.entry_id) if client.unsub_timer: client.unsub_timer() - for component in "sensor", "switch": - await hass.config_entries.async_forward_entry_unload(config_entry, component) + for platform in PLATFORMS: + await hass.config_entries.async_forward_entry_unload(config_entry, platform) - hass.data[DOMAIN].pop(config_entry.entry_id) + if not hass.data[DOMAIN]: + hass.services.async_remove(DOMAIN, SERVICE_ADD_TORRENT) return True @@ -128,14 +132,10 @@ class TransmissionClient: """Initialize the Transmission RPC API.""" self.hass = hass self.config_entry = config_entry + self.tm_api = None self._tm_data = None self.unsub_timer = None - @property - def service_name(self): - """Return the service name.""" - return slugify(f"{SERVICE_ADD_TORRENT}_{self.config_entry.data[CONF_NAME]}") - @property def api(self): """Return the tm_data object.""" @@ -145,20 +145,20 @@ class TransmissionClient: """Set up the Transmission client.""" try: - api = await get_api(self.hass, self.config_entry.data) + self.tm_api = await get_api(self.hass, self.config_entry.data) except CannotConnect: raise ConfigEntryNotReady except (AuthenticationError, UnknownError): return False - self._tm_data = TransmissionData(self.hass, self.config_entry, api) + self._tm_data = TransmissionData(self.hass, self.config_entry, self.tm_api) await self.hass.async_add_executor_job(self._tm_data.init_torrent_list) await self.hass.async_add_executor_job(self._tm_data.update) self.add_options() self.set_scan_interval(self.config_entry.options[CONF_SCAN_INTERVAL]) - for platform in ["sensor", "switch"]: + for platform in PLATFORMS: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( self.config_entry, platform @@ -167,18 +167,26 @@ class TransmissionClient: def add_torrent(service): """Add new torrent to download.""" + tm_client = None + for entry in self.hass.config_entries.async_entries(DOMAIN): + if entry.data[CONF_NAME] == service.data[CONF_NAME]: + tm_client = self.hass.data[DOMAIN][entry.entry_id] + break + if tm_client is None: + _LOGGER.error("Transmission instance is not found") + return torrent = service.data[ATTR_TORRENT] if torrent.startswith( ("http", "ftp:", "magnet:") ) or self.hass.config.is_allowed_path(torrent): - api.add_torrent(torrent) + tm_client.tm_api.add_torrent(torrent) else: _LOGGER.warning( "Could not add torrent: unsupported type or no permission" ) self.hass.services.async_register( - DOMAIN, self.service_name, add_torrent, schema=SERVICE_ADD_TORRENT_SCHEMA + DOMAIN, SERVICE_ADD_TORRENT, add_torrent, schema=SERVICE_ADD_TORRENT_SCHEMA ) self.config_entry.add_update_listener(self.async_options_updated) @@ -200,7 +208,7 @@ class TransmissionClient: def set_scan_interval(self, scan_interval): """Update scan interval.""" - async def refresh(event_time): + def refresh(event_time): """Get the latest data from Transmission.""" self._tm_data.update() @@ -240,9 +248,9 @@ class TransmissionData: return self.config.data[CONF_HOST] @property - def signal_options_update(self): - """Option update signal per transmission entry.""" - return f"tm-options-{self.host}" + def signal_update(self): + """Update signal per transmission entry.""" + return f"{DATA_UPDATED}-{self.host}" def update(self): """Get the latest data from Transmission instance.""" @@ -260,7 +268,7 @@ class TransmissionData: except TransmissionError: self.available = False _LOGGER.error("Unable to connect to Transmission client %s", self.host) - dispatcher_send(self.hass, self.signal_options_update) + dispatcher_send(self.hass, self.signal_update) def init_torrent_list(self): """Initialize torrent lists.""" diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py index d7b9efb15d8..193c152d7c1 100644 --- a/homeassistant/components/transmission/config_flow.py +++ b/homeassistant/components/transmission/config_flow.py @@ -16,9 +16,19 @@ from . import get_api from .const import DEFAULT_NAME, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DOMAIN from .errors import AuthenticationError, CannotConnect, UnknownError +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME, default=DEFAULT_NAME): str, + vol.Required(CONF_HOST): str, + vol.Optional(CONF_USERNAME): str, + vol.Optional(CONF_PASSWORD): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + } +) + class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a UniFi config flow.""" + """Handle Tansmission config flow.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL @@ -57,17 +67,7 @@ class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required(CONF_NAME, default=DEFAULT_NAME): str, - vol.Required(CONF_HOST): str, - vol.Optional(CONF_USERNAME): str, - vol.Optional(CONF_PASSWORD): str, - vol.Required(CONF_PORT, default=DEFAULT_PORT): int, - } - ), - errors=errors, + step_id="user", data_schema=DATA_SCHEMA, errors=errors, ) async def async_step_import(self, import_config): diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index 489582de157..c51d48eb532 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -52,6 +52,7 @@ class TransmissionSensor(Entity): self._data = None self.client_name = client_name self.type = sensor_type + self.unsub_update = None @property def name(self): @@ -92,9 +93,9 @@ class TransmissionSensor(Entity): async def async_added_to_hass(self): """Handle entity which will be added.""" - async_dispatcher_connect( + self.unsub_update = async_dispatcher_connect( self.hass, - self._tm_client.api.signal_options_update, + self._tm_client.api.signal_update, self._schedule_immediate_update, ) @@ -102,6 +103,12 @@ class TransmissionSensor(Entity): def _schedule_immediate_update(self): self.async_schedule_update_ha_state(True) + async def will_remove_from_hass(self): + """Unsubscribe from update dispatcher.""" + if self.unsub_update: + self.unsub_update() + self.unsub_update = None + def update(self): """Get the latest data from Transmission and updates the state.""" self._data = self._tm_client.api.data diff --git a/homeassistant/components/transmission/services.yaml b/homeassistant/components/transmission/services.yaml index ab383584e83..de3314e20f6 100644 --- a/homeassistant/components/transmission/services.yaml +++ b/homeassistant/components/transmission/services.yaml @@ -1,6 +1,9 @@ add_torrent: description: Add a new torrent to download (URL, magnet link or Base64 encoded). fields: + name: + description: Instance name as entered during entry config + example: Transmission torrent: description: URL, magnet link or Base64 encoded file. example: http://releases.ubuntu.com/19.04/ubuntu-19.04-desktop-amd64.iso.torrent diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index 4b93b3f06e2..adf94c64fd6 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -40,6 +40,7 @@ class TransmissionSwitch(ToggleEntity): self._tm_client = tm_client self._state = STATE_OFF self._data = None + self.unsub_update = None @property def name(self): @@ -93,9 +94,9 @@ class TransmissionSwitch(ToggleEntity): async def async_added_to_hass(self): """Handle entity which will be added.""" - async_dispatcher_connect( + self.unsub_update = async_dispatcher_connect( self.hass, - self._tm_client.api.signal_options_update, + self._tm_client.api.signal_update, self._schedule_immediate_update, ) @@ -103,6 +104,12 @@ class TransmissionSwitch(ToggleEntity): def _schedule_immediate_update(self): self.async_schedule_update_ha_state(True) + async def will_remove_from_hass(self): + """Unsubscribe from update dispatcher.""" + if self.unsub_update: + self.unsub_update() + self.unsub_update = None + def update(self): """Get the latest data from Transmission and updates the state.""" active = None diff --git a/tests/components/transmission/test_config_flow.py b/tests/components/transmission/test_config_flow.py index 28fbed9ff42..80e6bd55017 100644 --- a/tests/components/transmission/test_config_flow.py +++ b/tests/components/transmission/test_config_flow.py @@ -98,7 +98,6 @@ async def test_flow_works(hass, api): assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_PORT] == PORT - # assert result["data"]["options"][CONF_SCAN_INTERVAL] == DEFAULT_SCAN_INTERVAL # test with all provided result = await flow.async_step_user(MOCK_ENTRY) @@ -110,7 +109,6 @@ async def test_flow_works(hass, api): assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD assert result["data"][CONF_PORT] == PORT - # assert result["data"]["options"][CONF_SCAN_INTERVAL] == DEFAULT_SCAN_INTERVAL async def test_options(hass):