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

View file

@ -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."""

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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):