mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Standardize Plex server connections (#26444)
* Common connection class * Omit tests for new Plex files * Oops * Add missing properties * Remove redundant log message * Stopgap to avoid duplicate setups * Cleaner check for server setup Co-Authored-By: Martin Hjelmare <marhje52@kth.se> * Cleaner check for server setup Co-Authored-By: Martin Hjelmare <marhje52@kth.se> * Not needed with previous setup check * Remove username/password support * Reduce log level Co-Authored-By: Martin Hjelmare <marhje52@kth.se> * Don't do setup in __init__ * Oops * Committing too fast... * Connect after init * Catch update exceptions like media_player * Pass in validated PlexServer instance * Remove unnecessary check * Counter should be unknown on init * Remove servername config option
This commit is contained in:
parent
a000125729
commit
2cd845fb25
@ -470,8 +470,7 @@ omit =
|
|||||||
homeassistant/components/pioneer/media_player.py
|
homeassistant/components/pioneer/media_player.py
|
||||||
homeassistant/components/pjlink/media_player.py
|
homeassistant/components/pjlink/media_player.py
|
||||||
homeassistant/components/plaato/*
|
homeassistant/components/plaato/*
|
||||||
homeassistant/components/plex/media_player.py
|
homeassistant/components/plex/*
|
||||||
homeassistant/components/plex/sensor.py
|
|
||||||
homeassistant/components/plugwise/*
|
homeassistant/components/plugwise/*
|
||||||
homeassistant/components/plum_lightpad/*
|
homeassistant/components/plum_lightpad/*
|
||||||
homeassistant/components/pocketcasts/sensor.py
|
homeassistant/components/pocketcasts/sensor.py
|
||||||
|
15
homeassistant/components/plex/const.py
Normal file
15
homeassistant/components/plex/const.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"""Constants for the Plex component."""
|
||||||
|
DOMAIN = "plex"
|
||||||
|
NAME_FORMAT = "Plex {}"
|
||||||
|
|
||||||
|
DEFAULT_PORT = 32400
|
||||||
|
DEFAULT_SSL = False
|
||||||
|
DEFAULT_VERIFY_SSL = True
|
||||||
|
|
||||||
|
PLEX_CONFIG_FILE = "plex.conf"
|
||||||
|
PLEX_SERVER_CONFIG = "server_config"
|
||||||
|
|
||||||
|
CONF_USE_EPISODE_ART = "use_episode_art"
|
||||||
|
CONF_SHOW_ALL_CONTROLS = "show_all_controls"
|
||||||
|
CONF_REMOVE_UNAVAILABLE_CLIENTS = "remove_unavailable_clients"
|
||||||
|
CONF_CLIENT_REMOVE_INTERVAL = "client_remove_interval"
|
@ -2,8 +2,7 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import requests.exceptions
|
||||||
import requests
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA
|
from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA
|
||||||
@ -21,6 +20,9 @@ from homeassistant.components.media_player.const import (
|
|||||||
SUPPORT_VOLUME_SET,
|
SUPPORT_VOLUME_SET,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONF_URL,
|
||||||
|
CONF_TOKEN,
|
||||||
|
CONF_VERIFY_SSL,
|
||||||
DEVICE_DEFAULT_NAME,
|
DEVICE_DEFAULT_NAME,
|
||||||
STATE_IDLE,
|
STATE_IDLE,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
@ -32,18 +34,22 @@ from homeassistant.helpers.event import track_time_interval
|
|||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util.json import load_json, save_json
|
from homeassistant.util.json import load_json, save_json
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_USE_EPISODE_ART,
|
||||||
|
CONF_SHOW_ALL_CONTROLS,
|
||||||
|
CONF_REMOVE_UNAVAILABLE_CLIENTS,
|
||||||
|
CONF_CLIENT_REMOVE_INTERVAL,
|
||||||
|
DOMAIN as PLEX_DOMAIN,
|
||||||
|
NAME_FORMAT,
|
||||||
|
PLEX_CONFIG_FILE,
|
||||||
|
)
|
||||||
|
from .server import PlexServer
|
||||||
|
|
||||||
|
SERVER_SETUP = "server_setup"
|
||||||
|
|
||||||
_CONFIGURING = {}
|
_CONFIGURING = {}
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
NAME_FORMAT = "Plex {}"
|
|
||||||
PLEX_CONFIG_FILE = "plex.conf"
|
|
||||||
PLEX_DATA = "plex"
|
|
||||||
|
|
||||||
CONF_USE_EPISODE_ART = "use_episode_art"
|
|
||||||
CONF_SHOW_ALL_CONTROLS = "show_all_controls"
|
|
||||||
CONF_REMOVE_UNAVAILABLE_CLIENTS = "remove_unavailable_clients"
|
|
||||||
CONF_CLIENT_REMOVE_INTERVAL = "client_remove_interval"
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_USE_EPISODE_ART, default=False): cv.boolean,
|
vol.Optional(CONF_USE_EPISODE_ART, default=False): cv.boolean,
|
||||||
@ -58,8 +64,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
|
|
||||||
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
|
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
|
||||||
"""Set up the Plex platform."""
|
"""Set up the Plex platform."""
|
||||||
if PLEX_DATA not in hass.data:
|
plex_data = hass.data.setdefault(PLEX_DOMAIN, {})
|
||||||
hass.data[PLEX_DATA] = {}
|
server_setup = plex_data.setdefault(SERVER_SETUP, False)
|
||||||
|
if server_setup:
|
||||||
|
return
|
||||||
|
|
||||||
# get config from plex.conf
|
# get config from plex.conf
|
||||||
file_config = load_json(hass.config.path(PLEX_CONFIG_FILE))
|
file_config = load_json(hass.config.path(PLEX_CONFIG_FILE))
|
||||||
@ -102,20 +110,19 @@ def setup_plexserver(
|
|||||||
host, token, has_ssl, verify_ssl, hass, config, add_entities_callback
|
host, token, has_ssl, verify_ssl, hass, config, add_entities_callback
|
||||||
):
|
):
|
||||||
"""Set up a plexserver based on host parameter."""
|
"""Set up a plexserver based on host parameter."""
|
||||||
import plexapi.server
|
|
||||||
import plexapi.exceptions
|
import plexapi.exceptions
|
||||||
|
|
||||||
cert_session = None
|
|
||||||
http_prefix = "https" if has_ssl else "http"
|
http_prefix = "https" if has_ssl else "http"
|
||||||
if has_ssl and (verify_ssl is False):
|
|
||||||
_LOGGER.info("Ignoring SSL verification")
|
server_config = {
|
||||||
cert_session = requests.Session()
|
CONF_URL: f"{http_prefix}://{host}",
|
||||||
cert_session.verify = False
|
CONF_TOKEN: token,
|
||||||
|
CONF_VERIFY_SSL: verify_ssl,
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
plexserver = plexapi.server.PlexServer(
|
plexserver = PlexServer(server_config)
|
||||||
f"{http_prefix}://{host}", token, cert_session
|
plexserver.connect()
|
||||||
)
|
|
||||||
_LOGGER.info("Discovery configuration done (no token needed)")
|
|
||||||
except (
|
except (
|
||||||
plexapi.exceptions.BadRequest,
|
plexapi.exceptions.BadRequest,
|
||||||
plexapi.exceptions.Unauthorized,
|
plexapi.exceptions.Unauthorized,
|
||||||
@ -125,6 +132,8 @@ def setup_plexserver(
|
|||||||
# No token or wrong token
|
# No token or wrong token
|
||||||
request_configuration(host, hass, config, add_entities_callback)
|
request_configuration(host, hass, config, add_entities_callback)
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
hass.data[PLEX_DOMAIN][SERVER_SETUP] = True
|
||||||
|
|
||||||
# If we came here and configuring this host, mark as done
|
# If we came here and configuring this host, mark as done
|
||||||
if host in _CONFIGURING:
|
if host in _CONFIGURING:
|
||||||
@ -139,9 +148,7 @@ def setup_plexserver(
|
|||||||
{host: {"token": token, "ssl": has_ssl, "verify": verify_ssl}},
|
{host: {"token": token, "ssl": has_ssl, "verify": verify_ssl}},
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.info("Connected to: %s://%s", http_prefix, host)
|
plex_clients = {}
|
||||||
|
|
||||||
plex_clients = hass.data[PLEX_DATA]
|
|
||||||
plex_sessions = {}
|
plex_sessions = {}
|
||||||
track_time_interval(hass, lambda now: update_devices(), timedelta(seconds=10))
|
track_time_interval(hass, lambda now: update_devices(), timedelta(seconds=10))
|
||||||
|
|
||||||
|
@ -1,32 +1,30 @@
|
|||||||
"""Support for Plex media server monitoring."""
|
"""Support for Plex media server monitoring."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
import plexapi.exceptions
|
||||||
|
import requests.exceptions
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.switch import PLATFORM_SCHEMA
|
from homeassistant.components.switch import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_USERNAME,
|
|
||||||
CONF_PASSWORD,
|
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
CONF_TOKEN,
|
CONF_TOKEN,
|
||||||
CONF_SSL,
|
CONF_SSL,
|
||||||
|
CONF_URL,
|
||||||
CONF_VERIFY_SSL,
|
CONF_VERIFY_SSL,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
from .const import DEFAULT_PORT, DEFAULT_SSL, DEFAULT_VERIFY_SSL
|
||||||
|
from .server import PlexServer
|
||||||
CONF_SERVER = "server"
|
|
||||||
|
|
||||||
DEFAULT_HOST = "localhost"
|
DEFAULT_HOST = "localhost"
|
||||||
DEFAULT_NAME = "Plex"
|
DEFAULT_NAME = "Plex"
|
||||||
DEFAULT_PORT = 32400
|
_LOGGER = logging.getLogger(__name__)
|
||||||
DEFAULT_SSL = False
|
|
||||||
DEFAULT_VERIFY_SSL = True
|
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
|
||||||
|
|
||||||
@ -34,11 +32,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
{
|
{
|
||||||
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
vol.Optional(CONF_PASSWORD): cv.string,
|
|
||||||
vol.Optional(CONF_TOKEN): cv.string,
|
vol.Optional(CONF_TOKEN): cv.string,
|
||||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
vol.Optional(CONF_SERVER): cv.string,
|
|
||||||
vol.Optional(CONF_USERNAME): cv.string,
|
|
||||||
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
||||||
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
|
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
|
||||||
}
|
}
|
||||||
@ -48,34 +43,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the Plex sensor."""
|
"""Set up the Plex sensor."""
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
plex_user = config.get(CONF_USERNAME)
|
|
||||||
plex_password = config.get(CONF_PASSWORD)
|
|
||||||
plex_server = config.get(CONF_SERVER)
|
|
||||||
plex_host = config.get(CONF_HOST)
|
plex_host = config.get(CONF_HOST)
|
||||||
plex_port = config.get(CONF_PORT)
|
plex_port = config.get(CONF_PORT)
|
||||||
plex_token = config.get(CONF_TOKEN)
|
plex_token = config.get(CONF_TOKEN)
|
||||||
|
verify_ssl = config.get(CONF_VERIFY_SSL)
|
||||||
|
|
||||||
plex_url = "{}://{}:{}".format(
|
plex_url = "{}://{}:{}".format(
|
||||||
"https" if config.get(CONF_SSL) else "http", plex_host, plex_port
|
"https" if config.get(CONF_SSL) else "http", plex_host, plex_port
|
||||||
)
|
)
|
||||||
|
|
||||||
import plexapi.exceptions
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
add_entities(
|
plex_server = PlexServer(
|
||||||
[
|
{CONF_URL: plex_url, CONF_TOKEN: plex_token, CONF_VERIFY_SSL: verify_ssl}
|
||||||
PlexSensor(
|
|
||||||
name,
|
|
||||||
plex_url,
|
|
||||||
plex_user,
|
|
||||||
plex_password,
|
|
||||||
plex_server,
|
|
||||||
plex_token,
|
|
||||||
config.get(CONF_VERIFY_SSL),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
True,
|
|
||||||
)
|
)
|
||||||
|
plex_server.connect()
|
||||||
except (
|
except (
|
||||||
plexapi.exceptions.BadRequest,
|
plexapi.exceptions.BadRequest,
|
||||||
plexapi.exceptions.Unauthorized,
|
plexapi.exceptions.Unauthorized,
|
||||||
@ -84,43 +65,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
_LOGGER.error(error)
|
_LOGGER.error(error)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
add_entities([PlexSensor(name, plex_server)], True)
|
||||||
|
|
||||||
|
|
||||||
class PlexSensor(Entity):
|
class PlexSensor(Entity):
|
||||||
"""Representation of a Plex now playing sensor."""
|
"""Representation of a Plex now playing sensor."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, name, plex_server):
|
||||||
self,
|
|
||||||
name,
|
|
||||||
plex_url,
|
|
||||||
plex_user,
|
|
||||||
plex_password,
|
|
||||||
plex_server,
|
|
||||||
plex_token,
|
|
||||||
verify_ssl,
|
|
||||||
):
|
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
from plexapi.myplex import MyPlexAccount
|
|
||||||
from plexapi.server import PlexServer
|
|
||||||
from requests import Session
|
|
||||||
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._state = 0
|
self._state = None
|
||||||
self._now_playing = []
|
self._now_playing = []
|
||||||
|
self._server = plex_server
|
||||||
cert_session = None
|
|
||||||
if not verify_ssl:
|
|
||||||
_LOGGER.info("Ignoring SSL verification")
|
|
||||||
cert_session = Session()
|
|
||||||
cert_session.verify = False
|
|
||||||
|
|
||||||
if plex_token:
|
|
||||||
self._server = PlexServer(plex_url, plex_token, cert_session)
|
|
||||||
elif plex_user and plex_password:
|
|
||||||
user = MyPlexAccount(plex_user, plex_password)
|
|
||||||
server = plex_server if plex_server else user.resources()[0].name
|
|
||||||
self._server = user.resource(server).connect()
|
|
||||||
else:
|
|
||||||
self._server = PlexServer(plex_url, None, cert_session)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -145,7 +101,19 @@ class PlexSensor(Entity):
|
|||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update method for Plex sensor."""
|
"""Update method for Plex sensor."""
|
||||||
sessions = self._server.sessions()
|
try:
|
||||||
|
sessions = self._server.sessions()
|
||||||
|
except plexapi.exceptions.BadRequest:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Error listing current Plex sessions on %s", self._server.friendly_name
|
||||||
|
)
|
||||||
|
return
|
||||||
|
except requests.exceptions.RequestException as ex:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Temporary error connecting to %s (%s)", self._server.friendly_name, ex
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
now_playing = []
|
now_playing = []
|
||||||
for sess in sessions:
|
for sess in sessions:
|
||||||
user = sess.usernames[0]
|
user = sess.usernames[0]
|
||||||
|
59
homeassistant/components/plex/server.py
Normal file
59
homeassistant/components/plex/server.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"""Shared class to maintain Plex server instances."""
|
||||||
|
import logging
|
||||||
|
import plexapi.server
|
||||||
|
from requests import Session
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL
|
||||||
|
|
||||||
|
from .const import DEFAULT_VERIFY_SSL
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
|
||||||
|
class PlexServer:
|
||||||
|
"""Manages a single Plex server connection."""
|
||||||
|
|
||||||
|
def __init__(self, server_config):
|
||||||
|
"""Initialize a Plex server instance."""
|
||||||
|
self._plex_server = None
|
||||||
|
self._url = server_config.get(CONF_URL)
|
||||||
|
self._token = server_config.get(CONF_TOKEN)
|
||||||
|
self._verify_ssl = server_config.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Connect to a Plex server directly, obtaining direct URL if necessary."""
|
||||||
|
|
||||||
|
def _connect_with_url():
|
||||||
|
session = None
|
||||||
|
if self._url.startswith("https") and not self._verify_ssl:
|
||||||
|
session = Session()
|
||||||
|
session.verify = False
|
||||||
|
self._plex_server = plexapi.server.PlexServer(
|
||||||
|
self._url, self._token, session
|
||||||
|
)
|
||||||
|
_LOGGER.debug("Connected to: %s (%s)", self.friendly_name, self.url_in_use)
|
||||||
|
|
||||||
|
_connect_with_url()
|
||||||
|
|
||||||
|
def clients(self):
|
||||||
|
"""Pass through clients call to plexapi."""
|
||||||
|
return self._plex_server.clients()
|
||||||
|
|
||||||
|
def sessions(self):
|
||||||
|
"""Pass through sessions call to plexapi."""
|
||||||
|
return self._plex_server.sessions()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def friendly_name(self):
|
||||||
|
"""Return name of connected Plex server."""
|
||||||
|
return self._plex_server.friendlyName
|
||||||
|
|
||||||
|
@property
|
||||||
|
def machine_identifier(self):
|
||||||
|
"""Return unique identifier of connected Plex server."""
|
||||||
|
return self._plex_server.machineIdentifier
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_in_use(self):
|
||||||
|
"""Return URL used for connected Plex server."""
|
||||||
|
return self._plex_server._baseurl # pylint: disable=W0212
|
Loading…
x
Reference in New Issue
Block a user