Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions homeassistant/components/transmission/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"config": {
"title": "Transmission",
"step": {
"user": {
"title": "Setup Transmission Client",
"data": {
"name": "Name",
"host": "Host",
"username": "User name",
"password": "Password",
"port": "Port"
}
},
"options": {
"title": "Configure Options",
"data": {
"scan_interval": "Update frequency"
}
}
},
"error": {
"wrong_cerdentials": "Wrong username or password",
"cannot_connect": "Unable to Connect to host"
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary."
}
},
"options": {
"step": {
"init": {
"description": "Configure options for Transmission",
"data": {
"scan_interval": "Update frequency"
}
}
}
}
}
231 changes: 157 additions & 74 deletions homeassistant/components/transmission/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,38 @@
from datetime import timedelta
import logging

import transmissionrpc
from transmissionrpc.error import TransmissionError
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
CONF_HOST,
CONF_MONITORED_CONDITIONS,
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
)
from homeassistant.helpers import config_validation as cv, discovery
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 track_time_interval
from homeassistant.helpers.event import async_track_time_interval

from .const import (
ATTR_TORRENT,
DATA_TRANSMISSION,
DATA_UPDATED,
DEFAULT_NAME,
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
SERVICE_ADD_TORRENT,
)
from .errors import AuthenticationError, CannotConnect

_LOGGER = logging.getLogger(__name__)

DOMAIN = "transmission"
DATA_UPDATED = "transmission_data_updated"
DATA_TRANSMISSION = "data_transmission"

DEFAULT_NAME = "Transmission"
DEFAULT_PORT = 9091
TURTLE_MODE = "turtle_mode"

SENSOR_TYPES = {
"active_torrents": ["Active Torrents", None],
"current_status": ["Status", None],
"download_speed": ["Down Speed", "MB/s"],
"paused_torrents": ["Paused Torrents", None],
"total_torrents": ["Total Torrents", None],
"upload_speed": ["Up Speed", "MB/s"],
"completed_torrents": ["Completed Torrents", None],
"started_torrents": ["Started Torrents", None],
}

DEFAULT_SCAN_INTERVAL = timedelta(seconds=120)

ATTR_TORRENT = "torrent"

SERVICE_ADD_TORRENT = "add_torrent"

SERVICE_ADD_TORRENT_SCHEMA = vol.Schema({vol.Required(ATTR_TORRENT): cv.string})

Expand All @@ -55,97 +46,180 @@
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(TURTLE_MODE, default=False): cv.boolean,
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.time_period,
vol.Optional(
CONF_MONITORED_CONDITIONS, default=["current_status"]
): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
}
)
},
extra=vol.ALLOW_EXTRA,
)


def setup(hass, config):
async def async_setup(hass, config):
"""Import the Transmission Component from config."""
if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN]
)
)

return True


async def async_setup_entry(hass, config_entry):
"""Set up the Transmission Component."""
host = config[DOMAIN][CONF_HOST]
username = config[DOMAIN].get(CONF_USERNAME)
password = config[DOMAIN].get(CONF_PASSWORD)
port = config[DOMAIN][CONF_PORT]
scan_interval = config[DOMAIN][CONF_SCAN_INTERVAL]
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}

if not config_entry.options:
await async_populate_options(hass, config_entry)

client = TransmissionClient(hass, config_entry)
client_id = config_entry.entry_id
hass.data[DOMAIN][client_id] = client
if not await client.async_setup():
raise ConfigEntryNotReady

return True


async def async_unload_entry(hass, entry):
"""Unload Transmission Entry from config_entry."""
hass.services.async_remove(DOMAIN, SERVICE_ADD_TORRENT)
if hass.data[DOMAIN][entry.entry_id].unsub_timer:
hass.data[DOMAIN][entry.entry_id].unsub_timer()

for component in "sensor", "switch":
await hass.config_entries.async_forward_entry_unload(entry, component)

del hass.data[DOMAIN]

return True
Comment thread
engrbm87 marked this conversation as resolved.

import transmissionrpc
from transmissionrpc.error import TransmissionError

async def get_api(hass, host, port, username=None, password=None):
"""Get Transmission client."""
try:
api = transmissionrpc.Client(host, port=port, user=username, password=password)
api.session_stats()
api = await hass.async_add_executor_job(
transmissionrpc.Client, host, port, username, password
)
return api

except TransmissionError as error:
if str(error).find("401: Unauthorized"):
_LOGGER.error("Credentials for" " Transmission client are not valid")
if "401: Unauthorized" in str(error):
_LOGGER.error("Credentials for Transmission client are not valid")
raise AuthenticationError
if "111: Connection refused" in str(error):
_LOGGER.error("Connecting to the Transmission client failed")
raise CannotConnect
return False
Comment thread
MartinHjelmare marked this conversation as resolved.
Outdated

tm_data = hass.data[DATA_TRANSMISSION] = TransmissionData(hass, config, api)

tm_data.update()
tm_data.init_torrent_list()
async def async_populate_options(hass, config_entry):
"""Populate default options for Transmission Client."""
options = {CONF_SCAN_INTERVAL: config_entry.data["options"][CONF_SCAN_INTERVAL]}

hass.config_entries.async_update_entry(config_entry, options=options)

def refresh(event_time):
"""Get the latest data from Transmission."""
tm_data.update()

track_time_interval(hass, refresh, scan_interval)
class TransmissionClient:
"""Transmission Client Object."""

def add_torrent(service):
"""Add new torrent to download."""
torrent = service.data[ATTR_TORRENT]
if torrent.startswith(
("http", "ftp:", "magnet:")
) or hass.config.is_allowed_path(torrent):
api.add_torrent(torrent)
else:
_LOGGER.warning(
"Could not add torrent: " "unsupported type or no permission"
def __init__(self, hass, config_entry):
"""Initialize the Transmission RPC API."""
self.hass = hass
self.config_entry = config_entry
self.scan_interval = self.config_entry.options[CONF_SCAN_INTERVAL]
self.tm_data = None
self.unsub_timer = None

async def async_setup(self):
"""Set up the Transmission client."""

config = {
CONF_HOST: self.config_entry.data[CONF_HOST],
CONF_PORT: self.config_entry.data[CONF_PORT],
CONF_USERNAME: self.config_entry.data.get(CONF_USERNAME),
CONF_PASSWORD: self.config_entry.data.get(CONF_PASSWORD),
}
api = await get_api(self.hass, **config)
Comment thread
MartinHjelmare marked this conversation as resolved.
Outdated
if not api:
return False

self.tm_data = self.hass.data[DOMAIN][DATA_TRANSMISSION] = TransmissionData(
self.hass, self.config_entry, 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.set_scan_interval(self.scan_interval)

for platform in ["sensor", "switch"]:
self.hass.async_create_task(
self.hass.config_entries.async_forward_entry_setup(
self.config_entry, platform
)
)

hass.services.register(
DOMAIN, SERVICE_ADD_TORRENT, add_torrent, schema=SERVICE_ADD_TORRENT_SCHEMA
)
def add_torrent(service):
"""Add new torrent to download."""
torrent = service.data[ATTR_TORRENT]
if torrent.startswith(
("http", "ftp:", "magnet:")
) or self.hass.config.is_allowed_path(torrent):
api.add_torrent(torrent)
else:
_LOGGER.warning(
"Could not add torrent: unsupported type or no permission"
)

self.hass.services.async_register(
DOMAIN, SERVICE_ADD_TORRENT, add_torrent, schema=SERVICE_ADD_TORRENT_SCHEMA
)

sensorconfig = {
"sensors": config[DOMAIN][CONF_MONITORED_CONDITIONS],
"client_name": config[DOMAIN][CONF_NAME],
}
self.config_entry.add_update_listener(self.async_options_updated)

discovery.load_platform(hass, "sensor", DOMAIN, sensorconfig, config)
return True

if config[DOMAIN][TURTLE_MODE]:
discovery.load_platform(hass, "switch", DOMAIN, sensorconfig, config)
def set_scan_interval(self, scan_interval):
"""Update scan interval."""

return True
def refresh(event_time):
Comment thread
MartinHjelmare marked this conversation as resolved.
"""Get the latest data from Transmission."""
self.tm_data.update()

if self.unsub_timer is not None:
self.unsub_timer()
self.unsub_timer = async_track_time_interval(
self.hass, refresh, timedelta(seconds=scan_interval)
)

@staticmethod
async def async_options_updated(hass, entry):
"""Triggered by config entry options updates."""
hass.data[DOMAIN][entry.entry_id].set_scan_interval(
entry.options[CONF_SCAN_INTERVAL]
)


class TransmissionData:
"""Get the latest data and update the states."""

def __init__(self, hass, config, api):
"""Initialize the Transmission RPC API."""
self.hass = hass
self.data = None
self.torrents = None
self.session = None
self.available = True
self._api = api
self.completed_torrents = []
self.started_torrents = []
self.hass = hass

def update(self):
"""Get the latest data from Transmission instance."""
from transmissionrpc.error import TransmissionError

try:
self.data = self._api.session_stats()
self.torrents = self._api.get_torrents()
Expand Down Expand Up @@ -211,6 +285,15 @@ def get_completed_torrent_count(self):
"""Get the number of completed torrents."""
return len(self.completed_torrents)

def start_torrents(self):
"""Start all torrents."""
self._api.start_all()

def stop_torrents(self):
"""Stop all active torrents."""
torrent_ids = [torrent.id for torrent in self.torrents]
self._api.stop_torrent(torrent_ids)

def set_alt_speed_enabled(self, is_enabled):
"""Set the alternative speed flag."""
self._api.set_session(alt_speed_enabled=is_enabled)
Expand Down
Loading