From a236b87ccfb779cf4a41569843d41dc16383dcfb Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Tue, 13 Oct 2015 21:59:13 +0000 Subject: [PATCH 1/8] new attempt for PR --- homeassistant/components/media_player/plex.py | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 5fac9ecb0f0..33bab1954ee 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -16,9 +16,7 @@ from homeassistant.const import ( STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN) import homeassistant.util as util -REQUIREMENTS = ['https://github.com/adrienbrault/python-plexapi/archive/' - 'df2d0847e801d6d5cda920326d693cf75f304f1a.zip' - '#python-plexapi==1.0.2'] +REQUIREMENTS = ['plexapi==1.1.0'] MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) @@ -45,24 +43,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def update_devices(): """ Updates the devices objects. """ try: - devices = plexuser.devices() + devices = plexserver.clients() except BadRequest: _LOGGER.exception("Error listing plex devices") return new_plex_clients = [] for device in devices: - if (all(x not in ['client', 'player'] for x in device.provides) - or 'PlexAPI' == device.product): + # For now, let's allow all deviceClass types + if device.deviceClass in []: continue - if device.clientIdentifier not in plex_clients: + if device.machineIdentifier not in plex_clients: new_client = PlexClient(device, plex_sessions, update_devices, update_sessions) - plex_clients[device.clientIdentifier] = new_client + plex_clients[device.machineIdentifier] = new_client new_plex_clients.append(new_client) else: - plex_clients[device.clientIdentifier].set_device(device) + plex_clients[device.machineIdentifier].set_device(device) if new_plex_clients: add_devices(new_plex_clients) @@ -101,10 +99,10 @@ class PlexClient(MediaPlayerDevice): @property def session(self): """ Returns the session, if any. """ - if self.device.clientIdentifier not in self.plex_sessions: + if self.device.machineIdentifier not in self.plex_sessions: return None - return self.plex_sessions[self.device.clientIdentifier] + return self.plex_sessions[self.device.machineIdentifier] @property def name(self): @@ -120,7 +118,8 @@ class PlexClient(MediaPlayerDevice): return STATE_PLAYING elif state == 'paused': return STATE_PAUSED - elif self.device.isReachable: + # This is nasty. Need ti find a way to determine alive + elif self.device: return STATE_IDLE else: return STATE_OFF @@ -196,16 +195,16 @@ class PlexClient(MediaPlayerDevice): def media_play(self): """ media_play media player. """ - self.device.play({'type': 'video'}) + self.device.play() def media_pause(self): """ media_pause media player. """ - self.device.pause({'type': 'video'}) + self.device.pause() def media_next_track(self): """ Send next track command. """ - self.device.skipNext({'type': 'video'}) + self.device.skipNext() def media_previous_track(self): """ Send previous track command. """ - self.device.skipPrevious({'type': 'video'}) + self.device.skipPrevious() From 8e9cafd29d02488a62026a22b880194338e90760 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Fri, 16 Oct 2015 18:15:04 +0000 Subject: [PATCH 2/8] Updated requirements_all.txt. Added placeholder to the empty deviceClass filter. Will remove this if deemed unneeded, later --- homeassistant/components/media_player/plex.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 33bab1954ee..21890524348 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): new_plex_clients = [] for device in devices: # For now, let's allow all deviceClass types - if device.deviceClass in []: + if device.deviceClass in ['badClient']: continue if device.machineIdentifier not in plex_clients: diff --git a/requirements_all.txt b/requirements_all.txt index c63eea25853..11d91043d12 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -134,7 +134,7 @@ https://github.com/balloob/home-assistant-vera-api/archive/a8f823066ead6c7da6fb5 SoCo==0.11.1 # PlexAPI (media_player.plex) -https://github.com/adrienbrault/python-plexapi/archive/df2d0847e801d6d5cda920326d693cf75f304f1a.zip#python-plexapi==1.0.2 +plexapi==1.1.0 # SNMP (device_tracker.snmp) pysnmp==4.2.5 From db7e46abd1686194e3b0d61b8897d5a79704d061 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 18 Oct 2015 20:02:18 +0000 Subject: [PATCH 3/8] Intermediate save --- homeassistant/components/discovery.py | 2 + homeassistant/components/media_player/plex.py | 71 +++++++++++++++++-- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 0b3cc1025cc..9fc7ee6651c 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -28,6 +28,7 @@ SERVICE_HUE = 'philips_hue' SERVICE_CAST = 'google_cast' SERVICE_NETGEAR = 'netgear_router' SERVICE_SONOS = 'sonos' +SERVICE_PLEX = 'plex' SERVICE_HANDLERS = { SERVICE_WEMO: "switch", @@ -35,6 +36,7 @@ SERVICE_HANDLERS = { SERVICE_HUE: "light", SERVICE_NETGEAR: 'device_tracker', SERVICE_SONOS: 'media_player', + SERVICE_PLEX: 'media_player', } diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 21890524348..b18814a8ced 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -20,6 +20,8 @@ REQUIREMENTS = ['plexapi==1.1.0'] MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) +PLEX_CONFIG_FILE = 'plex.conf' + _LOGGER = logging.getLogger(__name__) SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK @@ -28,14 +30,73 @@ SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK # pylint: disable=abstract-method, unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the plex platform. """ + try: + # pylint: disable=unused-variable + from plexapi.myplex import MyPlexUser + from plexapi.exceptions import BadRequest + except ImportError: + _LOGGER.exception("Error while importing dependency plexapi.") + return + + if discovery_info is not None: + host = urlparse(discovery_info[1]).url + _LOGGER.error('Discovered PLEX server: %s'%host) + else: + # 'name' is currently used for plexserver + # This indicates old config method + host = config.get('name','') + + if host in _CONFIGURING: + return + + setup_plexserver(host, hass, add_devices) + + +def setup_plexserver(host, hass, add_devices): + ''' Setup a plexserver based on host parameter''' from plexapi.myplex import MyPlexUser + from plexapi.server import PlexServer from plexapi.exceptions import BadRequest - name = config.get('name', '') - user = config.get('user', '') - password = config.get('password', '') - plexuser = MyPlexUser.signin(user, password) - plexserver = plexuser.getResource(name).connect() + conf_file = hass.config.path(PHUE_CONFIG_FILE)) + + # Compatability mode. If there's name, user, etc set in + # configuration, let's use those, not to break anything + # We may want to use this method as option when HA's + # configuration options increase + if config.get('name', ''): + name = config.get('name', '') + user = config.get('user', '') + password = config.get('password', '') + plexuser = MyPlexUser.signin(user, password) + plexserver = plexuser.getResource(name).connect() + + # Discovery mode. Parse config file, attempt conenction + # Request configuration on connect fail + else: + + try: + # Get configuration from config file + # FIXME unauthenticated plex servers dont require + # a token, so config file isn't mandatory + with open(conf_file,'r') as f: + conf_dict = eval(f.read()) + + plexserver = PlexServer( + host, + conf_dict.get(host)['token']) + except IOError: # File not found + + except NotFound: # Wrong host was given or need token? + _LOGGER.exception("Error connecting to the Hue bridge at %s", host) + return + + except phue.PhueRegistrationException: + _LOGGER.warning("Connected to Hue at %s but not registered.", host) + + request_configuration(host, hass, add_devices_callback) + return + plex_clients = {} plex_sessions = {} From 6a82504e5e1890c67f8e08ce67a9f7e6ef9f19b4 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Tue, 20 Oct 2015 16:59:22 +0000 Subject: [PATCH 4/8] further discovery integration into plex --- homeassistant/components/discovery.py | 2 +- .../components/media_player/__init__.py | 1 + homeassistant/components/media_player/plex.py | 45 ++++++++++++++----- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 9fc7ee6651c..1e04f20ea3e 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -28,7 +28,7 @@ SERVICE_HUE = 'philips_hue' SERVICE_CAST = 'google_cast' SERVICE_NETGEAR = 'netgear_router' SERVICE_SONOS = 'sonos' -SERVICE_PLEX = 'plex' +SERVICE_PLEX = 'plex_mediaserver' SERVICE_HANDLERS = { SERVICE_WEMO: "switch", diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 294fccbb1f5..8040ef9c067 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -28,6 +28,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' DISCOVERY_PLATFORMS = { discovery.SERVICE_CAST: 'cast', discovery.SERVICE_SONOS: 'sonos', + discovery.SERVICE_PLEX: 'plex', } SERVICE_YOUTUBE_VIDEO = 'play_youtube_video' diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index b18814a8ced..ae619e64355 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -8,6 +8,7 @@ https://home-assistant.io/components/media_player.plex.html """ import logging from datetime import timedelta +from urllib.parse import urlparse from homeassistant.components.media_player import ( MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, @@ -22,6 +23,8 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) PLEX_CONFIG_FILE = 'plex.conf' +# Map ip to request id for configuring +_CONFIGURING = {} _LOGGER = logging.getLogger(__name__) SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK @@ -39,7 +42,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return if discovery_info is not None: - host = urlparse(discovery_info[1]).url + host = urlparse(discovery_info[1]).netloc _LOGGER.error('Discovered PLEX server: %s'%host) else: # 'name' is currently used for plexserver @@ -49,16 +52,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if host in _CONFIGURING: return - setup_plexserver(host, hass, add_devices) + setup_plexserver(host, config, hass, add_devices) def setup_plexserver(host, hass, add_devices): ''' Setup a plexserver based on host parameter''' - from plexapi.myplex import MyPlexUser from plexapi.server import PlexServer from plexapi.exceptions import BadRequest - conf_file = hass.config.path(PHUE_CONFIG_FILE)) + conf_file = hass.config.path(PLEX_CONFIG_FILE) # Compatability mode. If there's name, user, etc set in # configuration, let's use those, not to break anything @@ -75,6 +77,7 @@ def setup_plexserver(host, hass, add_devices): # Request configuration on connect fail else: + print('WEEEEJ, host: %s'%host) try: # Get configuration from config file # FIXME unauthenticated plex servers dont require @@ -83,20 +86,16 @@ def setup_plexserver(host, hass, add_devices): conf_dict = eval(f.read()) plexserver = PlexServer( - host, + 'http://%s'%host, conf_dict.get(host)['token']) except IOError: # File not found + request_configuration(host, hass, add_devices_callback) + return except NotFound: # Wrong host was given or need token? _LOGGER.exception("Error connecting to the Hue bridge at %s", host) return - except phue.PhueRegistrationException: - _LOGGER.warning("Connected to Hue at %s but not registered.", host) - - request_configuration(host, hass, add_devices_callback) - return - plex_clients = {} plex_sessions = {} @@ -143,6 +142,30 @@ def setup_plexserver(host, hass, add_devices): update_sessions() +def request_configuration(host, hass, add_devices_callback): + """ Request configuration steps from the user. """ + configurator = get_component('configurator') + + # We got an error if this method is called while we are configuring + if host in _CONFIGURING: + configurator.notify_errors( + _CONFIGURING[host], "Failed to register, please try again.") + + return + + def plex_configuration_callback(data): + """ Actions to do when our configuration callback is called. """ + setup_plexserrver(host, hass, add_devices_callback) + + _CONFIGURING[host] = configurator.request_config( + hass, "Plex Media Server", plex_configuration_callback, + description=("Enter the X-Plex-Token as descrobed here
" + 'Plex documentation'), + description_image="/static/images/config_plexserver.jpg", + submit_caption="I have pressed the button" + ) + + class PlexClient(MediaPlayerDevice): """ Represents a Plex device. """ From 884525df33ffb939b354177d6f99ead8ccb28840 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Thu, 22 Oct 2015 21:16:04 +0000 Subject: [PATCH 5/8] Basic discovery works, added plex logo for configurator. Missing configurator support for fields. Todo: config save on successful connect --- .../images/config_plex_mediaserver.png | Bin 0 -> 18619 bytes homeassistant/components/media_player/plex.py | 102 ++++++++---------- 2 files changed, 47 insertions(+), 55 deletions(-) create mode 100644 homeassistant/components/frontend/www_static/images/config_plex_mediaserver.png diff --git a/homeassistant/components/frontend/www_static/images/config_plex_mediaserver.png b/homeassistant/components/frontend/www_static/images/config_plex_mediaserver.png new file mode 100644 index 0000000000000000000000000000000000000000..97a1b4b352cdbf2629e1612db15e30bcf2af18a4 GIT binary patch literal 18619 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" - 'Plex documentation'), - description_image="/static/images/config_plexserver.jpg", - submit_caption="I have pressed the button" + description=('Enter the X-Plex-Token'), + description_image="/static/images/config_plex_mediaserver.png", + submit_caption="Confirm", + fields=[{'Token':'token'}] ) From 847d9736aa002a6db025d0b4633eccf373231b56 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 25 Oct 2015 10:45:15 +0000 Subject: [PATCH 6/8] Configurator works, config saving basic implementation --- homeassistant/components/media_player/plex.py | 129 +++++++++++------- 1 file changed, 83 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index 4651a963fea..dc9b7e82070 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -6,7 +6,7 @@ Provides an interface to the Plex API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.plex.html """ -import logging +import logging, json, os from datetime import timedelta from urllib.parse import urlparse @@ -31,60 +31,91 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK +def config_from_file(filename, config=None): + ''' Small configuration file management function''' + if config: + # We're writing configuration + try: + with open(filename,'w') as f: + f.write(json.dumps(config)) + except IOError as e: + _LOGGER.error('Saving config file failed: %s'%e) + return False + return True + else: + # We're reading config + if os.path.isfile(filename): + try: + with open(filename,'r') as f: + return json.loads(f.read()) + except IOError as e: + _LOGGER.error('Reading config file failed: %s'%e) + return False + else: + return {} + # pylint: disable=abstract-method, unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Sets up the plex platform. """ + # Via discovery if discovery_info is not None: # Parse discovery data host = urlparse(discovery_info[1]).netloc _LOGGER.info('Discovered PLEX server: %s'%host) - else: - host = config.get(CONF_HOST, None) - if host in _CONFIGURING: - return - - setup_plexserver(host, hass, add_devices_callback) - - -def setup_plexserver(host, hass, add_devices_callback): - ''' Setup a plexserver based on host parameter''' - import plexapi - - # Config parsing & discovery mix - conf_file = hass.config.path(PLEX_CONFIG_FILE) - try: - with open(conf_file,'r') as f: - conf_dict = eval(f.read()) - except IOError: # File not found - if host == None: - # No discovery, no config, quit here + if host in _CONFIGURING: return - conf_dict = {} - - if host == None: - # Called by module inclusion, let's only use config - host,token = conf_dict.popitem() - token = token['token'] - elif host not in conf_dict.keys(): - # Not in config - conf_dict[host] = { 'token' : '' } token = None + else: + # Setup a configured PlexServer + config = config_from_file(hass.config.path(PLEX_CONFIG_FILE)) + if len(config): + host,token = config.popitem() + token = token['token'] + else: + # Empty config file? + return + + setup_plexserver(host, token, hass, add_devices_callback) + + +def setup_plexserver(host, token, hass, add_devices_callback): + ''' Setup a plexserver based on host parameter''' + from plexapi.server import PlexServer + from plexapi.exceptions import BadRequest + import plexapi + _LOGGER.info('Connecting to: htts://%s using token: %s' % (host, token)) try: - plexserver = plexapi.PlexServer('http://%s'%host, token) - except Exception: + plexserver = plexapi.server.PlexServer('http://%s'%host, token) + except (plexapi.exceptions.BadRequest, + plexapi.exceptions.Unauthorized, + plexapi.exceptions.NotFound) as e: + _LOGGER.info(e) + # No token or wrong token request_configuration(host, hass, add_devices_callback) return - except plexapi.exceptions.BadRequest as e: - _LOGGER.error('BLABLA1') - request_configuration(host, hass, add_devices_callback) + except Exception as e: + _LOGGER.error('Misc Exception : %s'%e) return + # If we came here and configuring this host, mark as done + if host in _CONFIGURING: + request_id = _CONFIGURING.pop(host) + configurator = get_component('configurator') + configurator.request_done(request_id) + _LOGGER.info('Discovery configuration done!') + + # Save config + if not config_from_file( + hass.config.path(PLEX_CONFIG_FILE), + {host: {'token': token}}): + _LOGGER.error('failed to save config file') + _LOGGER.info('Connected to: htts://%s using token: %s' % (host, token)) @@ -96,7 +127,7 @@ def setup_plexserver(host, hass, add_devices_callback): """ Updates the devices objects. """ try: devices = plexserver.clients() - except BadRequest: + except plexapi.exceptions.BadRequest: _LOGGER.exception("Error listing plex devices") return @@ -115,14 +146,14 @@ def setup_plexserver(host, hass, add_devices_callback): plex_clients[device.machineIdentifier].set_device(device) if new_plex_clients: - add_devices(new_plex_clients) + add_devices_callback(new_plex_clients) @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update_sessions(): """ Updates the sessions objects. """ try: sessions = plexserver.sessions() - except BadRequest: + except plexapi.exceptions.BadRequest: _LOGGER.exception("Error listing plex sessions") return @@ -147,14 +178,14 @@ def request_configuration(host, hass, add_devices_callback): def plex_configuration_callback(data): """ Actions to do when our configuration callback is called. """ - setup_plexserver(host, hass, add_devices_callback) + setup_plexserver(host, data.get('token'), hass, add_devices_callback) _CONFIGURING[host] = configurator.request_config( hass, "Plex Media Server", plex_configuration_callback, description=('Enter the X-Plex-Token'), description_image="/static/images/config_plex_mediaserver.png", submit_caption="Confirm", - fields=[{'Token':'token'}] + fields=[{'id': 'token', 'name':'X-Plex-Token', 'type':''}] ) @@ -172,6 +203,17 @@ class PlexClient(MediaPlayerDevice): """ Sets the device property. """ self.device = device + @property + def unique_id(self): + """ Returns the id of this plex client """ + return "{}.{}".format( + self.__class__, self.device.machineIdentifier or self.device.name ) + + @property + def name(self): + """ Returns the name of the device. """ + return self.device.name or self.device.product or self.device.deviceClass + @property def session(self): """ Returns the session, if any. """ @@ -180,11 +222,6 @@ class PlexClient(MediaPlayerDevice): return self.plex_sessions[self.device.machineIdentifier] - @property - def name(self): - """ Returns the name of the device. """ - return self.device.name or self.device.product or self.device.device - @property def state(self): """ Returns the state of the device. """ @@ -194,7 +231,7 @@ class PlexClient(MediaPlayerDevice): return STATE_PLAYING elif state == 'paused': return STATE_PAUSED - # This is nasty. Need ti find a way to determine alive + # This is nasty. Need to find a way to determine alive elif self.device: return STATE_IDLE else: From 5b25d9ccd6e1fa8d82ce3971390b2677ac12fea2 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 25 Oct 2015 17:00:54 +0000 Subject: [PATCH 7/8] flake8,pylint and other code cleanup --- homeassistant/components/discovery.py | 1 + homeassistant/components/media_player/plex.py | 57 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 1e04f20ea3e..b75abc32e4b 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -90,6 +90,7 @@ def setup(hass, config): ATTR_DISCOVERED: info }) + # pylint: disable=unused-argument def start_discovery(event): """ Start discovering. """ netdisco = DiscoveryService(SCAN_INTERVAL) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index dc9b7e82070..eeed715b337 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -6,7 +6,9 @@ Provides an interface to the Plex API. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.plex.html """ -import logging, json, os +import os +import json +import logging from datetime import timedelta from urllib.parse import urlparse @@ -16,7 +18,7 @@ from homeassistant.components.media_player import ( MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) from homeassistant.const import ( - CONF_HOST, DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_PLAYING, + DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN) REQUIREMENTS = ['plexapi==1.1.0'] @@ -31,29 +33,30 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK + def config_from_file(filename, config=None): ''' Small configuration file management function''' if config: # We're writing configuration try: - with open(filename,'w') as f: - f.write(json.dumps(config)) - except IOError as e: - _LOGGER.error('Saving config file failed: %s'%e) + with open(filename, 'w') as fdesc: + fdesc.write(json.dumps(config)) + except IOError as error: + _LOGGER.error('Saving config file failed: %s', error) return False return True else: # We're reading config if os.path.isfile(filename): try: - with open(filename,'r') as f: - return json.loads(f.read()) - except IOError as e: - _LOGGER.error('Reading config file failed: %s'%e) + with open(filename, 'r') as fdesc: + return json.loads(fdesc.read()) + except IOError as error: + _LOGGER.error('Reading config file failed: %s', error) return False else: return {} - + # pylint: disable=abstract-method, unused-argument def setup_platform(hass, config, add_devices_callback, discovery_info=None): @@ -63,7 +66,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): if discovery_info is not None: # Parse discovery data host = urlparse(discovery_info[1]).netloc - _LOGGER.info('Discovered PLEX server: %s'%host) + _LOGGER.info('Discovered PLEX server: %s', host) if host in _CONFIGURING: return @@ -73,7 +76,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # Setup a configured PlexServer config = config_from_file(hass.config.path(PLEX_CONFIG_FILE)) if len(config): - host,token = config.popitem() + host, token = config.popitem() token = token['token'] else: # Empty config file? @@ -82,25 +85,22 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): setup_plexserver(host, token, hass, add_devices_callback) +# pylint: disable=too-many-branches def setup_plexserver(host, token, hass, add_devices_callback): ''' Setup a plexserver based on host parameter''' - from plexapi.server import PlexServer - from plexapi.exceptions import BadRequest import plexapi - _LOGGER.info('Connecting to: htts://%s using token: %s' % - (host, token)) try: - plexserver = plexapi.server.PlexServer('http://%s'%host, token) + plexserver = plexapi.server.PlexServer('http://%s' % host, token) except (plexapi.exceptions.BadRequest, - plexapi.exceptions.Unauthorized, - plexapi.exceptions.NotFound) as e: - _LOGGER.info(e) + plexapi.exceptions.Unauthorized) as error: + _LOGGER.info(error) # No token or wrong token request_configuration(host, hass, add_devices_callback) return - except Exception as e: - _LOGGER.error('Misc Exception : %s'%e) + except plexapi.exceptions.NotFound: + # Host not found. Maybe it's off. Just log it and stop + _LOGGER.info(error) return # If we came here and configuring this host, mark as done @@ -116,8 +116,7 @@ def setup_plexserver(host, token, hass, add_devices_callback): {host: {'token': token}}): _LOGGER.error('failed to save config file') - _LOGGER.info('Connected to: htts://%s using token: %s' % - (host, token)) + _LOGGER.info('Connected to: htts://%s', host) plex_clients = {} plex_sessions = {} @@ -185,14 +184,14 @@ def request_configuration(host, hass, add_devices_callback): description=('Enter the X-Plex-Token'), description_image="/static/images/config_plex_mediaserver.png", submit_caption="Confirm", - fields=[{'id': 'token', 'name':'X-Plex-Token', 'type':''}] + fields=[{'id': 'token', 'name': 'X-Plex-Token', 'type': ''}] ) class PlexClient(MediaPlayerDevice): """ Represents a Plex device. """ - # pylint: disable=too-many-public-methods + # pylint: disable=too-many-public-methods, attribute-defined-outside-init def __init__(self, device, plex_sessions, update_devices, update_sessions): self.plex_sessions = plex_sessions self.update_devices = update_devices @@ -207,12 +206,12 @@ class PlexClient(MediaPlayerDevice): def unique_id(self): """ Returns the id of this plex client """ return "{}.{}".format( - self.__class__, self.device.machineIdentifier or self.device.name ) + self.__class__, self.device.machineIdentifier or self.device.name) @property def name(self): """ Returns the name of the device. """ - return self.device.name or self.device.product or self.device.deviceClass + return self.device.name or DEVICE_DEFAULT_NAME @property def session(self): From bc8c5766d4c320dd341ffc6b2169ae408ca06bb9 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 25 Oct 2015 17:54:48 +0000 Subject: [PATCH 8/8] Logic fixes --- homeassistant/components/media_player/plex.py | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index eeed715b337..b8267d286d3 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -53,6 +53,7 @@ def config_from_file(filename, config=None): return json.loads(fdesc.read()) except IOError as error: _LOGGER.error('Reading config file failed: %s', error) + # This won't work yet return False else: return {} @@ -62,8 +63,13 @@ def config_from_file(filename, config=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Sets up the plex platform. """ + config = config_from_file(hass.config.path(PLEX_CONFIG_FILE)) + if len(config): + # Setup a configured PlexServer + host, token = config.popitem() + token = token['token'] # Via discovery - if discovery_info is not None: + elif discovery_info is not None: # Parse discovery data host = urlparse(discovery_info[1]).netloc _LOGGER.info('Discovered PLEX server: %s', host) @@ -71,16 +77,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): if host in _CONFIGURING: return token = None - else: - # Setup a configured PlexServer - config = config_from_file(hass.config.path(PLEX_CONFIG_FILE)) - if len(config): - host, token = config.popitem() - token = token['token'] - else: - # Empty config file? - return + return setup_plexserver(host, token, hass, add_devices_callback) @@ -88,20 +86,18 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): # pylint: disable=too-many-branches def setup_plexserver(host, token, hass, add_devices_callback): ''' Setup a plexserver based on host parameter''' - import plexapi + import plexapi.server + import plexapi.exceptions try: plexserver = plexapi.server.PlexServer('http://%s' % host, token) except (plexapi.exceptions.BadRequest, - plexapi.exceptions.Unauthorized) as error: + plexapi.exceptions.Unauthorized, + plexapi.exceptions.NotFound) as error: _LOGGER.info(error) # No token or wrong token request_configuration(host, hass, add_devices_callback) return - except plexapi.exceptions.NotFound: - # Host not found. Maybe it's off. Just log it and stop - _LOGGER.info(error) - return # If we came here and configuring this host, mark as done if host in _CONFIGURING: