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
This commit is contained in:
Rami Mosleh 2019-11-26 21:22:12 +04:00 committed by Paulus Schoutsen
parent f8a36499c1
commit a5960830d7
7 changed files with 79 additions and 64 deletions

View File

@ -1,42 +1,34 @@
{ {
"config": { "config": {
"abort": { "title": "Transmission",
"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"
},
"step": { "step": {
"options": {
"data": {
"scan_interval": "Update frequency"
},
"title": "Configure Options"
},
"user": { "user": {
"title": "Setup Transmission Client",
"data": { "data": {
"host": "Host",
"name": "Name", "name": "Name",
"host": "Host",
"username": "Username",
"password": "Password", "password": "Password",
"port": "Port", "port": "Port"
"username": "Username" }
},
"title": "Setup Transmission Client"
} }
}, },
"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": { "options": {
"step": { "step": {
"init": { "init": {
"title": "Configure options for Transmission",
"data": { "data": {
"scan_interval": "Update frequency" "scan_interval": "Update frequency"
}, }
"description": "Configure options for Transmission",
"title": "Configure options for Transmission"
} }
} }
} }

View File

@ -19,7 +19,6 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import slugify
from .const import ( from .const import (
ATTR_TORRENT, ATTR_TORRENT,
@ -28,13 +27,16 @@ from .const import (
DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
DOMAIN, DOMAIN,
SERVICE_ADD_TORRENT, SERVICE_ADD_TORRENT,
DATA_UPDATED,
) )
from .errors import AuthenticationError, CannotConnect, UnknownError from .errors import AuthenticationError, CannotConnect, UnknownError
_LOGGER = logging.getLogger(__name__) _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( TRANS_SCHEMA = vol.All(
vol.Schema( vol.Schema(
@ -55,6 +57,8 @@ CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.All(cv.ensure_list, [TRANS_SCHEMA])}, extra=vol.ALLOW_EXTRA {DOMAIN: vol.All(cv.ensure_list, [TRANS_SCHEMA])}, extra=vol.ALLOW_EXTRA
) )
PLATFORMS = ["sensor", "switch"]
async def async_setup(hass, config): async def async_setup(hass, config):
"""Import the Transmission Component from 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): async def async_unload_entry(hass, config_entry):
"""Unload Transmission Entry from config_entry.""" """Unload Transmission Entry from config_entry."""
client = hass.data[DOMAIN][config_entry.entry_id] client = hass.data[DOMAIN].pop(config_entry.entry_id)
hass.services.async_remove(DOMAIN, client.service_name)
if client.unsub_timer: if client.unsub_timer:
client.unsub_timer() client.unsub_timer()
for component in "sensor", "switch": for platform in PLATFORMS:
await hass.config_entries.async_forward_entry_unload(config_entry, component) 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 return True
@ -128,14 +132,10 @@ class TransmissionClient:
"""Initialize the Transmission RPC API.""" """Initialize the Transmission RPC API."""
self.hass = hass self.hass = hass
self.config_entry = config_entry self.config_entry = config_entry
self.tm_api = None
self._tm_data = None self._tm_data = None
self.unsub_timer = 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 @property
def api(self): def api(self):
"""Return the tm_data object.""" """Return the tm_data object."""
@ -145,20 +145,20 @@ class TransmissionClient:
"""Set up the Transmission client.""" """Set up the Transmission client."""
try: 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: except CannotConnect:
raise ConfigEntryNotReady raise ConfigEntryNotReady
except (AuthenticationError, UnknownError): except (AuthenticationError, UnknownError):
return False 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.init_torrent_list)
await self.hass.async_add_executor_job(self._tm_data.update) await self.hass.async_add_executor_job(self._tm_data.update)
self.add_options() self.add_options()
self.set_scan_interval(self.config_entry.options[CONF_SCAN_INTERVAL]) 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.async_create_task(
self.hass.config_entries.async_forward_entry_setup( self.hass.config_entries.async_forward_entry_setup(
self.config_entry, platform self.config_entry, platform
@ -167,18 +167,26 @@ class TransmissionClient:
def add_torrent(service): def add_torrent(service):
"""Add new torrent to download.""" """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] torrent = service.data[ATTR_TORRENT]
if torrent.startswith( if torrent.startswith(
("http", "ftp:", "magnet:") ("http", "ftp:", "magnet:")
) or self.hass.config.is_allowed_path(torrent): ) or self.hass.config.is_allowed_path(torrent):
api.add_torrent(torrent) tm_client.tm_api.add_torrent(torrent)
else: else:
_LOGGER.warning( _LOGGER.warning(
"Could not add torrent: unsupported type or no permission" "Could not add torrent: unsupported type or no permission"
) )
self.hass.services.async_register( 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) self.config_entry.add_update_listener(self.async_options_updated)
@ -200,7 +208,7 @@ class TransmissionClient:
def set_scan_interval(self, scan_interval): def set_scan_interval(self, scan_interval):
"""Update scan interval.""" """Update scan interval."""
async def refresh(event_time): def refresh(event_time):
"""Get the latest data from Transmission.""" """Get the latest data from Transmission."""
self._tm_data.update() self._tm_data.update()
@ -240,9 +248,9 @@ class TransmissionData:
return self.config.data[CONF_HOST] return self.config.data[CONF_HOST]
@property @property
def signal_options_update(self): def signal_update(self):
"""Option update signal per transmission entry.""" """Update signal per transmission entry."""
return f"tm-options-{self.host}" return f"{DATA_UPDATED}-{self.host}"
def update(self): def update(self):
"""Get the latest data from Transmission instance.""" """Get the latest data from Transmission instance."""
@ -260,7 +268,7 @@ class TransmissionData:
except TransmissionError: except TransmissionError:
self.available = False self.available = False
_LOGGER.error("Unable to connect to Transmission client %s", self.host) _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): def init_torrent_list(self):
"""Initialize torrent lists.""" """Initialize torrent lists."""

View File

@ -16,9 +16,19 @@ from . import get_api
from .const import DEFAULT_NAME, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DOMAIN from .const import DEFAULT_NAME, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DOMAIN
from .errors import AuthenticationError, CannotConnect, UnknownError 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): class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a UniFi config flow.""" """Handle Tansmission config flow."""
VERSION = 1 VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
@ -57,17 +67,7 @@ class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
) )
return self.async_show_form( return self.async_show_form(
step_id="user", step_id="user", data_schema=DATA_SCHEMA, errors=errors,
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,
) )
async def async_step_import(self, import_config): async def async_step_import(self, import_config):

View File

@ -52,6 +52,7 @@ class TransmissionSensor(Entity):
self._data = None self._data = None
self.client_name = client_name self.client_name = client_name
self.type = sensor_type self.type = sensor_type
self.unsub_update = None
@property @property
def name(self): def name(self):
@ -92,9 +93,9 @@ class TransmissionSensor(Entity):
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Handle entity which will be added.""" """Handle entity which will be added."""
async_dispatcher_connect( self.unsub_update = async_dispatcher_connect(
self.hass, self.hass,
self._tm_client.api.signal_options_update, self._tm_client.api.signal_update,
self._schedule_immediate_update, self._schedule_immediate_update,
) )
@ -102,6 +103,12 @@ class TransmissionSensor(Entity):
def _schedule_immediate_update(self): def _schedule_immediate_update(self):
self.async_schedule_update_ha_state(True) 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): def update(self):
"""Get the latest data from Transmission and updates the state.""" """Get the latest data from Transmission and updates the state."""
self._data = self._tm_client.api.data self._data = self._tm_client.api.data

View File

@ -1,6 +1,9 @@
add_torrent: add_torrent:
description: Add a new torrent to download (URL, magnet link or Base64 encoded). description: Add a new torrent to download (URL, magnet link or Base64 encoded).
fields: fields:
name:
description: Instance name as entered during entry config
example: Transmission
torrent: torrent:
description: URL, magnet link or Base64 encoded file. description: URL, magnet link or Base64 encoded file.
example: http://releases.ubuntu.com/19.04/ubuntu-19.04-desktop-amd64.iso.torrent example: http://releases.ubuntu.com/19.04/ubuntu-19.04-desktop-amd64.iso.torrent

View File

@ -40,6 +40,7 @@ class TransmissionSwitch(ToggleEntity):
self._tm_client = tm_client self._tm_client = tm_client
self._state = STATE_OFF self._state = STATE_OFF
self._data = None self._data = None
self.unsub_update = None
@property @property
def name(self): def name(self):
@ -93,9 +94,9 @@ class TransmissionSwitch(ToggleEntity):
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Handle entity which will be added.""" """Handle entity which will be added."""
async_dispatcher_connect( self.unsub_update = async_dispatcher_connect(
self.hass, self.hass,
self._tm_client.api.signal_options_update, self._tm_client.api.signal_update,
self._schedule_immediate_update, self._schedule_immediate_update,
) )
@ -103,6 +104,12 @@ class TransmissionSwitch(ToggleEntity):
def _schedule_immediate_update(self): def _schedule_immediate_update(self):
self.async_schedule_update_ha_state(True) 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): def update(self):
"""Get the latest data from Transmission and updates the state.""" """Get the latest data from Transmission and updates the state."""
active = None active = None

View File

@ -98,7 +98,6 @@ async def test_flow_works(hass, api):
assert result["data"][CONF_NAME] == NAME assert result["data"][CONF_NAME] == NAME
assert result["data"][CONF_HOST] == HOST assert result["data"][CONF_HOST] == HOST
assert result["data"][CONF_PORT] == PORT assert result["data"][CONF_PORT] == PORT
# assert result["data"]["options"][CONF_SCAN_INTERVAL] == DEFAULT_SCAN_INTERVAL
# test with all provided # test with all provided
result = await flow.async_step_user(MOCK_ENTRY) 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_USERNAME] == USERNAME
assert result["data"][CONF_PASSWORD] == PASSWORD assert result["data"][CONF_PASSWORD] == PASSWORD
assert result["data"][CONF_PORT] == PORT assert result["data"][CONF_PORT] == PORT
# assert result["data"]["options"][CONF_SCAN_INTERVAL] == DEFAULT_SCAN_INTERVAL
async def test_options(hass): async def test_options(hass):