From 5cdda0e334c4c168402350a65cd5ee8ed3ef7b6e Mon Sep 17 00:00:00 2001 From: croneter Date: Sun, 30 Sep 2018 13:16:51 +0200 Subject: [PATCH] Revert "First attempt at server select dialog" This reverts commit 59040f3b3e7b926b1073e6ba836159a3f8330fcb. --- resources/lib/plex_functions.py | 124 ++++++---- resources/lib/plexapi/__init__.py | 1 - resources/lib/plexapi/base.py | 126 ---------- resources/lib/utils.py | 40 ---- resources/lib/windows/server_select.py | 224 ------------------ .../Main/1080i/script-plex-server_select.xml | 156 ------------ 6 files changed, 72 insertions(+), 599 deletions(-) delete mode 100644 resources/lib/plexapi/__init__.py delete mode 100644 resources/lib/plexapi/base.py delete mode 100644 resources/lib/windows/server_select.py delete mode 100644 resources/skins/Main/1080i/script-plex-server_select.xml diff --git a/resources/lib/plex_functions.py b/resources/lib/plex_functions.py index 80b672fc..f9a5d9f9 100644 --- a/resources/lib/plex_functions.py +++ b/resources/lib/plex_functions.py @@ -11,8 +11,9 @@ from threading import Thread from xbmc import sleep from .downloadutils import DownloadUtils as DU -from .plexapi.base import PlexServer, Connection -from . import utils, plex_tv, variables as v +from . import utils +from . import plex_tv +from . import variables as v ############################################################################### LOG = getLogger('PLEX.plex_functions') @@ -192,7 +193,7 @@ def discover_pms(token=None): """ LOG.info('Start discovery of Plex Media Servers') # Look first for local PMS in the LAN - local_pms_list = plex_gdm() + local_pms_list = _plex_gdm() LOG.debug('PMS found in the local LAN using Plex GDM: %s', local_pms_list) # Get PMS from plex.tv if token: @@ -235,7 +236,7 @@ def _log_pms(pms_list): LOG.debug('Found the following PMS: %s', log_list) -def plex_gdm(): +def _plex_gdm(): """ PlexGDM - looks for PMS in the local LAN and returns a list of the PMS found """ @@ -277,54 +278,44 @@ def plex_gdm(): # Check if we had a positive HTTP response if '200 OK' not in response['data']: continue - connection = Connection(local=True) - # Local LAN IP from GDM - connection.address = response['from'][0] - pms = PlexServer() - pms.presence = True - pms.connections.append(connection) + pms = { + 'ip': response['from'][0], + 'scheme': None, + 'local': True, # Since we found it using GDM + 'product': None, + 'baseURL': None, + 'name': None, + 'version': None, + 'token': None, + 'ownername': None, + 'device': None, + 'platform': None, + 'owned': None, + 'relay': None, + 'presence': True, # Since we're talking to the PMS + 'httpsRequired': None, + } for line in response['data'].split('\n'): - try: - kind, info = line.split(':', 1) - except ValueError: - continue - else: - kind, info = kind.strip(), info.strip() - if kind == 'Name': - pms.name = info - elif kind == 'Resource-Identifier': - pms.clientIdentifier = info - elif kind == 'Content-Type': - pms.product = info - elif kind == 'Version': - pms.productVersion = info - elif kind == 'Updated-At': - pms.lastSeenAt = int(info) - elif kind == 'Host': - # Example: "Host: <....sfe...>.plex.direct" - connection.uri = info - elif kind == 'Port': - connection.port = int(info) - # Assume https - connection.protocol = 'https' - if connection.uri != connection.address: - # The PMS might return both local IP and plex.direct address - alt_connection = deepcopy(connection) - alt_connection.uri = 'https://%s:%s' % (connection.address, - connection.port) - pms.connections.append(alt_connection) - connection.uri = 'https://%s:%s' % (connection.uri, - connection.port) + if 'Content-Type:' in line: + pms['product'] = utils.try_decode(line.split(':')[1].strip()) + elif 'Host:' in line: + pms['baseURL'] = line.split(':')[1].strip() + elif 'Name:' in line: + pms['name'] = utils.try_decode(line.split(':')[1].strip()) + elif 'Port:' in line: + pms['port'] = line.split(':')[1].strip() + elif 'Resource-Identifier:' in line: + pms['machineIdentifier'] = line.split(':')[1].strip() + elif 'Version:' in line: + pms['version'] = line.split(':')[1].strip() pms_list.append(pms) - LOG.debug('Found PMS in the LAN: %s: %s', pms, pms.connections) return pms_list -def pms_from_plex_tv(token): +def _pms_list_from_plex_tv(token): """ get Plex media Server List from plex.tv/pms/resources """ - pms_list = [] xml = DU().downloadUrl('https://plex.tv/api/resources', authenticate=False, parameters={'includeHttps': 1}, @@ -333,20 +324,49 @@ def pms_from_plex_tv(token): xml.attrib except AttributeError: LOG.error('Could not get list of PMS from plex.tv') - return pms_list + return [] + + from Queue import Queue + queue = Queue() + thread_queue = [] + + max_age_in_seconds = 2 * 60 * 60 * 24 for device in xml.findall('Device'): - if 'server' not in device.get('provides', ''): + if 'server' not in device.get('provides'): # No PMS - skip continue if device.find('Connection') is None: # no valid connection - skip continue - pms = PlexServer(xml=device) - pms_list.append(pms) - return pms_list - - - + # check MyPlex data age - skip if >2 days + info_age = time() - int(device.get('lastSeenAt')) + if info_age > max_age_in_seconds: + LOG.debug("Skip server %s not seen for 2 days", device.get('name')) + continue + pms = { + 'machineIdentifier': device.get('clientIdentifier'), + 'name': device.get('name'), + 'token': device.get('accessToken'), + 'ownername': device.get('sourceTitle'), + 'product': device.get('product'), # e.g. 'Plex Media Server' + 'version': device.get('productVersion'), # e.g. '1.11.2.4772-3e..' + 'device': device.get('device'), # e.g. 'PC' or 'Windows' + 'platform': device.get('platform'), # e.g. 'Windows', 'Android' + 'local': device.get('publicAddressMatches') == '1', + 'owned': device.get('owned') == '1', + 'relay': device.get('relay') == '1', + 'presence': device.get('presence') == '1', + 'httpsRequired': device.get('httpsRequired') == '1', + 'connections': [] + } + # Try a local connection first, no matter what plex.tv tells us + for connection in device.findall('Connection'): + if connection.get('local') == '1': + pms['connections'].append(connection) + # Then try non-local + for connection in device.findall('Connection'): + if connection.get('local') != '1': + pms['connections'].append(connection) # Spawn threads to ping each PMS simultaneously thread = Thread(target=_poke_pms, args=(pms, queue)) thread_queue.append(thread) diff --git a/resources/lib/plexapi/__init__.py b/resources/lib/plexapi/__init__.py deleted file mode 100644 index b93054b3..00000000 --- a/resources/lib/plexapi/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Dummy file to make this directory a package. diff --git a/resources/lib/plexapi/base.py b/resources/lib/plexapi/base.py deleted file mode 100644 index 101d8686..00000000 --- a/resources/lib/plexapi/base.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -""" -from __future__ import absolute_import, division, unicode_literals -from logging import getLogger - -from ..utils import cast, to_list - -LOG = getLogger('PLEX.' + __name__) - - -class Connection(object): - def __init__(self, xml=None, **kwargs): - self.protocol = None - self.address = None - self.port = None - self.uri = None - self.local = None - if xml: - self.load_from_xml(xml) - # Set all remaining attributes set on Class instantiation - for key, value in kwargs.items(): - setattr(self, key, value) - - def __unicode__(self): - return ''.format(self=self) - - def __repr__(self): - return self.__unicode__().encode('utf-8') - - def __eq__(self, other): - return self.uri == other.uri - - def __ne__(self, other): - return self.uri != other.uri - - def load_from_xml(self, xml): - """ - Throw in an etree xml-element to load PMS settings from it - """ - if xml.tag != 'Connection': - raise RuntimeError('Did not receive Connection xml but %s' - % xml.tag) - self.protocol = cast(unicode, xml.get('protocol')) - self.address = cast(unicode, xml.get('address')) - self.port = cast(int, xml.get('port')) - self.uri = cast(unicode, xml.get('uri')) - self.local = cast(bool, xml.get('local')) - - -class PlexServer(object): - def __init__(self, xml=None, **kwargs): - # Information from plex.tv - self.name = None - self.clientIdentifier = None - self.provides = set() - self.owned = None - self.home = None - self.httpsRequired = None - self.synced = None - self.relay = None - self.publicAddressMatches = None - self.presence = None - self.accessToken = None - - self.product = None # Usually "Plex Media Server" - self.ownerId = None # User id of the owner of this PMS - self.owner = None # User name of the (foreign!) owner - self.productVersion = None - self.platform = None - self.platformVersion = None - self.device = None - self.createdAt = None - self.lastSeenAt = None - - # Connection info - self.connections = [] - if xml: - self.load_from_xml(xml) - # Set all remaining attributes set on Class instantiation - for key, value in kwargs.items(): - setattr(self, key, value) - - def load_from_xml(self, xml): - """ - Throw in an etree xml-element to load PMS settings from it - """ - if xml.tag != 'Device': - raise RuntimeError('Did not receive Device xml but %s' % xml.tag) - self.name = cast(unicode, xml.get('name')) - self.clientIdentifier = cast(unicode, xml.get('clientIdentifier')) - self.provides = set(to_list(cast(unicode, xml.get('provides')))) - self.owned = cast(bool, xml.get('owned')) - self.home = cast(bool, xml.get('home')) - self.httpsRequired = cast(bool, xml.get('httpsRequired')) - self.synced = cast(bool, xml.get('synced')) - self.relay = cast(bool, xml.get('relay')) - self.publicAddressMatches = cast(bool, - xml.get('publicAddressMatches')) - self.presence = cast(bool, xml.get('presence')) - self.accessToken = cast(unicode, xml.get('accessToken')) - self.product = cast(unicode, xml.get('product')) - self.ownerId = cast(int, xml.get('ownerId')) - self.owner = cast(unicode, xml.get('sourceTitle')) - self.productVersion = cast(unicode, xml.get('productVersion')) - self.platform = cast(unicode, xml.get('platform')) - self.platformVersion = cast(unicode, xml.get('platformVersion')) - self.device = cast(unicode, xml.get('device')) - self.createdAt = cast(int, xml.get('createdAt')) - self.lastSeenAt = cast(int, xml.get('lastSeenAt')) - - for connection in xml.findall('Connection'): - self.connections.append(Connection(xml=connection)) - - def __unicode__(self): - return ''.format(self=self) - - def __repr__(self): - return self.__unicode__().encode('utf-8') - - def __eq__(self, other): - return self.clientIdentifier == other.clientIdentifier - - def __ne__(self, other): - return self.clientIdentifier != other.clientIdentifier diff --git a/resources/lib/utils.py b/resources/lib/utils.py index e466cce0..62bd4f4a 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -249,46 +249,6 @@ def ERROR(txt='', hide_tb=False, notify=False): return short -def to_list(value, itemcast=None, delim=','): - """ - Returns a list of unicodes from the specified value [unicode]. - - Parameters: - value [unicode]: (comma) delimited string to convert to list. - itemcast (func): Function to cast each list item to (default unicode). - delim (str): string delimiter (optional; default ','). - """ - value = value or '' - itemcast = itemcast or unicode - return [itemcast(item) for item in value.split(delim) if item != ''] - - -def cast(func, value): - """ - Cast the specified value to the specified type (returned by func). - Currently supports int, float, bool, unicode (if str supplied), str - Will return None if value=None - - Parameters: - func (func): Calback function to used cast to type - value (any): value to be cast and returned. - """ - if value is None: - return value - if func == bool: - return bool(int(value)) - elif func in (int, float): - try: - return func(value) - except ValueError: - return float('nan') - elif func == unicode: - return value.decode('utf-8') - elif func == str: - return value.encode('utf-8') - return func(value) - - class AttributeDict(dict): """ Turns an etree xml response's xml.attrib into an object with attributes diff --git a/resources/lib/windows/server_select.py b/resources/lib/windows/server_select.py deleted file mode 100644 index 9a36f174..00000000 --- a/resources/lib/windows/server_select.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -:module: plexkodiconnect.userselect -:synopsis: This module shows a dialog to let one choose between different Plex - (home) users -""" -from __future__ import absolute_import, division, unicode_literals -from logging import getLogger -import xbmc -import xbmcgui - -from . import kodigui -from .connection import plexapp -from .. import backgroundthread, utils, plex_functions as PF, variables as v - -LOG = getLogger('PLEX.' + __name__) - - -class UserThumbTask(backgroundthread.Task): - def setup(self, users, callback): - self.users = users - self.callback = callback - return self - - def run(self): - for user in self.users: - if self.isCanceled(): - return - thumb, back = user.thumb, '' - self.callback(user, thumb, back) - - -class ServerListItem(kodigui.ManagedListItem): - def init(self): - self.dataSource.on('completed:reachability', self.onUpdate) - self.dataSource.on('started:reachability', self.onUpdate) - return self - - def safeSetProperty(self, key, value): - # For if we catch the item in the middle of being removed - try: - self.setProperty(key, value) - return True - except AttributeError: - return False - - def safeSetLabel(self, value): - # For if we catch the item in the middle of being removed - try: - self.setLabel(value) - return True - except AttributeError: - return False - - def onUpdate(self, **kwargs): - if not self.listItem: # ex. can happen on Kodi shutdown - return - - if not self.dataSource.isSupported or not self.dataSource.isReachable(): - if self.dataSource.pendingReachabilityRequests > 0: - self.safeSetProperty('status', 'refreshing.gif') - else: - self.safeSetProperty('status', 'unreachable.png') - else: - self.safeSetProperty('status', self.dataSource.isSecure and 'secure.png' or '') - - self.safeSetProperty('current', plexapp.SERVERMANAGER.selectedServer == self.dataSource and '1' or '') - self.safeSetLabel(self.dataSource.name) - - def onDestroy(self): - self.dataSource.off('completed:reachability', self.onUpdate) - self.dataSource.off('started:reachability', self.onUpdate) - - -class ServerSelectWindow(kodigui.BaseWindow): - xmlFile = 'script-plex-server_select.xml' - path = v.ADDON_PATH - theme = 'Main' - res = '1080i' - width = 1920 - height = 1080 - - USER_LIST_ID = 101 - PIN_ENTRY_GROUP_ID = 400 - HOME_BUTTON_ID = 500 - SERVER_LIST_ID = 260 - - def __init__(self, *args, **kwargs): - self.tasks = None - self.server = None - self.aborted = False - self.serverList = None - kodigui.BaseWindow.__init__(self, *args, **kwargs) - - def onFirstInit(self): - self.serverList = kodigui.ManagedControlList(self, - self.SERVER_LIST_ID, - 10) - self.start() - - def onAction(self, action): - try: - ID = action.getId() - if 57 < ID < 68: - if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.PIN_ENTRY_GROUP_ID)): - item = self.userList.getSelectedItem() - if not item.dataSource.isProtected: - return - self.setFocusId(self.PIN_ENTRY_GROUP_ID) - self.pinEntryClicked(ID + 142) - return - elif 142 <= ID <= 149: # JumpSMS action - if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.PIN_ENTRY_GROUP_ID)): - item = self.userList.getSelectedItem() - if not item.dataSource.isProtected: - return - self.setFocusId(self.PIN_ENTRY_GROUP_ID) - self.pinEntryClicked(ID + 60) - return - elif ID in (xbmcgui.ACTION_NAV_BACK, xbmcgui.ACTION_BACKSPACE): - if xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.PIN_ENTRY_GROUP_ID)): - self.pinEntryClicked(211) - return - except: - utils.ERROR() - - kodigui.BaseWindow.onAction(self, action) - - def onClick(self, controlID): - if controlID == self.USER_LIST_ID: - item = self.userList.getSelectedItem() - if item.dataSource.isProtected: - self.setFocusId(self.PIN_ENTRY_GROUP_ID) - else: - self.userSelected(item) - elif 200 < controlID < 212: - self.pinEntryClicked(controlID) - elif controlID == self.HOME_BUTTON_ID: - self.home_button_clicked() - - def onFocus(self, controlID): - if controlID == self.USER_LIST_ID: - item = self.userList.getSelectedItem() - item.setProperty('editing.pin', '') - - def showServers(self, from_refresh=False, mouse=False): - selection = None - if from_refresh: - mli = self.serverList.getSelectedItem() - if mli: - selection = mli.dataSource - else: - plexapp.refreshResources() - - servers = sorted( - plexapp.SERVERMANAGER.getServers(), - key=lambda x: (x.owned and '0' or '1') + x.name.lower() - ) - servers = PF.plex_gdm() - - items = [] - for s in servers: - item = ServerListItem(s.name, - not s.owned and s.owner or '', - data_source=s).init() - item.onUpdate() - item.setProperty( - 'current', - plexapp.SERVERMANAGER.selectedServer == s and '1' or '') - items.append(item) - - if len(items) > 1: - items[0].setProperty('first', '1') - items[-1].setProperty('last', '1') - elif items: - items[0].setProperty('only', '1') - - self.serverList.replaceItems(items) - - self.getControl(800).setHeight((min(len(items), 9) * 100) + 80) - - if selection: - for mli in self.serverList: - if mli.dataSource == selection: - self.serverList.selectItem(mli.pos()) - if not from_refresh and items and not mouse: - self.setFocusId(self.SERVER_LIST_ID) - - def start(self): - self.setProperty('busy', '1') - self.showServers() - self.setProperty('initialized', '1') - self.setProperty('busy', '') - - def home_button_clicked(self): - """ - Action taken if user clicked the home button - """ - self.server = None - self.aborted = True - self.doClose() - - def finished(self): - for task in self.tasks: - task.cancel() - - -def start(): - """ - Hit this function to open a dialog to choose the Plex user - - Returns - ======= - tuple (server, aborted) - server : PlexServer - Or None if server switch failed or aborted by the server - aborted : bool - True if the server cancelled the dialog - """ - w = ServerSelectWindow.open() - server, aborted = w.server, w.aborted - del w - return server, aborted diff --git a/resources/skins/Main/1080i/script-plex-server_select.xml b/resources/skins/Main/1080i/script-plex-server_select.xml deleted file mode 100644 index bd800471..00000000 --- a/resources/skins/Main/1080i/script-plex-server_select.xml +++ /dev/null @@ -1,156 +0,0 @@ - - - 100 - - 1 - 0 - 0 - - 0xff111111 - - - - - 0 - 0 - 1920 - 1080 - script.plex/home/background-fallback.png - - - 0 - 0 - 1920 - 1080 - 1000 - $INFO[Window.Property(background)] - - - - - - 201 - 0 - 0 - 1920 - 135 - - 0 - 0 - 1920 - 135 - plugin.video.plexkodiconnect/white-square.png - 19000000 - - - - 20 - -5.5 - 1040 - 170 - left - 60 - horizontal - 101 - true - - 40 - 34.5 - 124 - 66 - - 0 - 0 - 124 - 66 - 101 - 101 - right - center - - - - - - - - !String.IsEmpty(Window.Property(dropdown)) - 0 - 0 - 124 - 66 - plugin.video.plexkodiconnect/white-square-rounded.png - - - 27 - 13 - - !Control.HasFocus(500) + String.IsEmpty(Window.Property(dropdown)) - 0 - 0 - 90 - 90 - plugin.video.plexkodiconnect/home/type/home.png - - - Control.HasFocus(500) | !String.IsEmpty(Window.Property(dropdown)) - -40 - -40 - 170 - 170 - plugin.video.plexkodiconnect/home/type/home-selected.png - - - - - 13 - 50 - auto - 66 - font12 - left - center - FFFFFFFF - - - - - - 213 - 35 - 200 - 65 - font12 - right - center - FFFFFFFF - - - - 153r - 54 - 93 - 30 - plugin.video.plexkodiconnect/home/plex.png - - - - - - !String.IsEmpty(Window.Property(busy)) - - 840 - 465 - 240 - 150 - script.plex/busy-back.png - A0FFFFFF - - - 915 - 521 - 90 - 38 - script.plex/busy.gif - - - -