First attempt at server select dialog
This commit is contained in:
parent
32927931c4
commit
59040f3b3e
6 changed files with 599 additions and 72 deletions
|
@ -11,9 +11,8 @@ from threading import Thread
|
||||||
from xbmc import sleep
|
from xbmc import sleep
|
||||||
|
|
||||||
from .downloadutils import DownloadUtils as DU
|
from .downloadutils import DownloadUtils as DU
|
||||||
from . import utils
|
from .plexapi.base import PlexServer, Connection
|
||||||
from . import plex_tv
|
from . import utils, plex_tv, variables as v
|
||||||
from . import variables as v
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
LOG = getLogger('PLEX.plex_functions')
|
LOG = getLogger('PLEX.plex_functions')
|
||||||
|
@ -193,7 +192,7 @@ def discover_pms(token=None):
|
||||||
"""
|
"""
|
||||||
LOG.info('Start discovery of Plex Media Servers')
|
LOG.info('Start discovery of Plex Media Servers')
|
||||||
# Look first for local PMS in the LAN
|
# 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)
|
LOG.debug('PMS found in the local LAN using Plex GDM: %s', local_pms_list)
|
||||||
# Get PMS from plex.tv
|
# Get PMS from plex.tv
|
||||||
if token:
|
if token:
|
||||||
|
@ -236,7 +235,7 @@ def _log_pms(pms_list):
|
||||||
LOG.debug('Found the following PMS: %s', log_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
|
PlexGDM - looks for PMS in the local LAN and returns a list of the PMS found
|
||||||
"""
|
"""
|
||||||
|
@ -278,44 +277,54 @@ def _plex_gdm():
|
||||||
# Check if we had a positive HTTP response
|
# Check if we had a positive HTTP response
|
||||||
if '200 OK' not in response['data']:
|
if '200 OK' not in response['data']:
|
||||||
continue
|
continue
|
||||||
pms = {
|
connection = Connection(local=True)
|
||||||
'ip': response['from'][0],
|
# Local LAN IP from GDM
|
||||||
'scheme': None,
|
connection.address = response['from'][0]
|
||||||
'local': True, # Since we found it using GDM
|
pms = PlexServer()
|
||||||
'product': None,
|
pms.presence = True
|
||||||
'baseURL': None,
|
pms.connections.append(connection)
|
||||||
'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'):
|
for line in response['data'].split('\n'):
|
||||||
if 'Content-Type:' in line:
|
try:
|
||||||
pms['product'] = utils.try_decode(line.split(':')[1].strip())
|
kind, info = line.split(':', 1)
|
||||||
elif 'Host:' in line:
|
except ValueError:
|
||||||
pms['baseURL'] = line.split(':')[1].strip()
|
continue
|
||||||
elif 'Name:' in line:
|
else:
|
||||||
pms['name'] = utils.try_decode(line.split(':')[1].strip())
|
kind, info = kind.strip(), info.strip()
|
||||||
elif 'Port:' in line:
|
if kind == 'Name':
|
||||||
pms['port'] = line.split(':')[1].strip()
|
pms.name = info
|
||||||
elif 'Resource-Identifier:' in line:
|
elif kind == 'Resource-Identifier':
|
||||||
pms['machineIdentifier'] = line.split(':')[1].strip()
|
pms.clientIdentifier = info
|
||||||
elif 'Version:' in line:
|
elif kind == 'Content-Type':
|
||||||
pms['version'] = line.split(':')[1].strip()
|
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)
|
||||||
pms_list.append(pms)
|
pms_list.append(pms)
|
||||||
|
LOG.debug('Found PMS in the LAN: %s: %s', pms, pms.connections)
|
||||||
return pms_list
|
return pms_list
|
||||||
|
|
||||||
|
|
||||||
def _pms_list_from_plex_tv(token):
|
def pms_from_plex_tv(token):
|
||||||
"""
|
"""
|
||||||
get Plex media Server List from plex.tv/pms/resources
|
get Plex media Server List from plex.tv/pms/resources
|
||||||
"""
|
"""
|
||||||
|
pms_list = []
|
||||||
xml = DU().downloadUrl('https://plex.tv/api/resources',
|
xml = DU().downloadUrl('https://plex.tv/api/resources',
|
||||||
authenticate=False,
|
authenticate=False,
|
||||||
parameters={'includeHttps': 1},
|
parameters={'includeHttps': 1},
|
||||||
|
@ -324,49 +333,20 @@ def _pms_list_from_plex_tv(token):
|
||||||
xml.attrib
|
xml.attrib
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
LOG.error('Could not get list of PMS from plex.tv')
|
LOG.error('Could not get list of PMS from plex.tv')
|
||||||
return []
|
return pms_list
|
||||||
|
|
||||||
from Queue import Queue
|
|
||||||
queue = Queue()
|
|
||||||
thread_queue = []
|
|
||||||
|
|
||||||
max_age_in_seconds = 2 * 60 * 60 * 24
|
|
||||||
for device in xml.findall('Device'):
|
for device in xml.findall('Device'):
|
||||||
if 'server' not in device.get('provides'):
|
if 'server' not in device.get('provides', ''):
|
||||||
# No PMS - skip
|
# No PMS - skip
|
||||||
continue
|
continue
|
||||||
if device.find('Connection') is None:
|
if device.find('Connection') is None:
|
||||||
# no valid connection - skip
|
# no valid connection - skip
|
||||||
continue
|
continue
|
||||||
# check MyPlex data age - skip if >2 days
|
pms = PlexServer(xml=device)
|
||||||
info_age = time() - int(device.get('lastSeenAt'))
|
pms_list.append(pms)
|
||||||
if info_age > max_age_in_seconds:
|
return pms_list
|
||||||
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
|
# Spawn threads to ping each PMS simultaneously
|
||||||
thread = Thread(target=_poke_pms, args=(pms, queue))
|
thread = Thread(target=_poke_pms, args=(pms, queue))
|
||||||
thread_queue.append(thread)
|
thread_queue.append(thread)
|
||||||
|
|
1
resources/lib/plexapi/__init__.py
Normal file
1
resources/lib/plexapi/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Dummy file to make this directory a package.
|
126
resources/lib/plexapi/base.py
Normal file
126
resources/lib/plexapi/base.py
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
#!/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 '<Connection {self.uri}>'.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 '<PlexServer {self.name}:{self.clientIdentifier}>'.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
|
|
@ -249,6 +249,46 @@ def ERROR(txt='', hide_tb=False, notify=False):
|
||||||
return short
|
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):
|
class AttributeDict(dict):
|
||||||
"""
|
"""
|
||||||
Turns an etree xml response's xml.attrib into an object with attributes
|
Turns an etree xml response's xml.attrib into an object with attributes
|
||||||
|
|
224
resources/lib/windows/server_select.py
Normal file
224
resources/lib/windows/server_select.py
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
#!/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
|
156
resources/skins/Main/1080i/script-plex-server_select.xml
Normal file
156
resources/skins/Main/1080i/script-plex-server_select.xml
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<window>
|
||||||
|
<defaultcontrol>100</defaultcontrol>
|
||||||
|
<coordinates>
|
||||||
|
<system>1</system>
|
||||||
|
<posx>0</posx>
|
||||||
|
<posy>0</posy>
|
||||||
|
</coordinates>
|
||||||
|
<backgroundcolor>0xff111111</backgroundcolor>
|
||||||
|
<controls>
|
||||||
|
<!-- Background -->
|
||||||
|
<control type="group">
|
||||||
|
<control type="image">
|
||||||
|
<posx>0</posx>
|
||||||
|
<posy>0</posy>
|
||||||
|
<width>1920</width>
|
||||||
|
<height>1080</height>
|
||||||
|
<texture colordiffuse="80FFFFFF">script.plex/home/background-fallback.png</texture>
|
||||||
|
</control>
|
||||||
|
<control type="image">
|
||||||
|
<posx>0</posx>
|
||||||
|
<posy>0</posy>
|
||||||
|
<width>1920</width>
|
||||||
|
<height>1080</height>
|
||||||
|
<fadetime>1000</fadetime>
|
||||||
|
<texture background="true">$INFO[Window.Property(background)]</texture>
|
||||||
|
</control>
|
||||||
|
</control>
|
||||||
|
|
||||||
|
<!-- Top bar -->
|
||||||
|
<control type="group">
|
||||||
|
<defaultcontrol always="true">201</defaultcontrol>
|
||||||
|
<posx>0</posx>
|
||||||
|
<posy>0</posy>
|
||||||
|
<width>1920</width>
|
||||||
|
<height>135</height>
|
||||||
|
<control type="image">
|
||||||
|
<posx>0</posx>
|
||||||
|
<posy>0</posy>
|
||||||
|
<width>1920</width>
|
||||||
|
<height>135</height>
|
||||||
|
<texture>plugin.video.plexkodiconnect/white-square.png</texture>
|
||||||
|
<colordiffuse>19000000</colordiffuse>
|
||||||
|
</control>
|
||||||
|
|
||||||
|
<control type="grouplist">
|
||||||
|
<posx>20</posx>
|
||||||
|
<posy>-5.5</posy>
|
||||||
|
<width>1040</width>
|
||||||
|
<height>170</height>
|
||||||
|
<align>left</align>
|
||||||
|
<itemgap>60</itemgap>
|
||||||
|
<orientation>horizontal</orientation>
|
||||||
|
<ondown>101</ondown>
|
||||||
|
<usecontrolcoords>true</usecontrolcoords>
|
||||||
|
<control type="group">
|
||||||
|
<posx>40</posx>
|
||||||
|
<posy>34.5</posy>
|
||||||
|
<width>124</width>
|
||||||
|
<height>66</height>
|
||||||
|
<control type="button" id="500">
|
||||||
|
<posx>0</posx>
|
||||||
|
<posy>0</posy>
|
||||||
|
<width>124</width>
|
||||||
|
<height>66</height>
|
||||||
|
<ondown>101</ondown>
|
||||||
|
<onright>101</onright>
|
||||||
|
<align>right</align>
|
||||||
|
<aligny>center</aligny>
|
||||||
|
<texturefocus>-</texturefocus>
|
||||||
|
<texturenofocus>-</texturenofocus>
|
||||||
|
<label> </label>
|
||||||
|
</control>
|
||||||
|
<control type="image">
|
||||||
|
<visible>!String.IsEmpty(Window.Property(dropdown))</visible>
|
||||||
|
<posx>0</posx>
|
||||||
|
<posy>0</posy>
|
||||||
|
<width>124</width>
|
||||||
|
<height>66</height>
|
||||||
|
<texture colordiffuse="FFCC7B19" border="10">plugin.video.plexkodiconnect/white-square-rounded.png</texture>
|
||||||
|
</control>
|
||||||
|
<control type="group">
|
||||||
|
<posx>27</posx>
|
||||||
|
<posy>13</posy>
|
||||||
|
<control type="image">
|
||||||
|
<visible>!Control.HasFocus(500) + String.IsEmpty(Window.Property(dropdown))</visible>
|
||||||
|
<posx>0</posx>
|
||||||
|
<posy>0</posy>
|
||||||
|
<width>90</width>
|
||||||
|
<height>90</height>
|
||||||
|
<texture>plugin.video.plexkodiconnect/home/type/home.png</texture>
|
||||||
|
</control>
|
||||||
|
<control type="image">
|
||||||
|
<visible>Control.HasFocus(500) | !String.IsEmpty(Window.Property(dropdown))</visible>
|
||||||
|
<posx>-40</posx>
|
||||||
|
<posy>-40</posy>
|
||||||
|
<width>170</width>
|
||||||
|
<height>170</height>
|
||||||
|
<texture>plugin.video.plexkodiconnect/home/type/home-selected.png</texture>
|
||||||
|
</control>
|
||||||
|
</control>
|
||||||
|
</control>
|
||||||
|
<control type="label">
|
||||||
|
<posx>13</posx>
|
||||||
|
<posy>50</posy>
|
||||||
|
<width max="500">auto</width>
|
||||||
|
<height>66</height>
|
||||||
|
<font>font12</font>
|
||||||
|
<align>left</align>
|
||||||
|
<aligny>center</aligny>
|
||||||
|
<textcolor>FFFFFFFF</textcolor>
|
||||||
|
<label>[UPPERCASE]$ADDON[plugin.video.plexkodiconnect 33000][/UPPERCASE]</label>
|
||||||
|
</control>
|
||||||
|
</control>
|
||||||
|
|
||||||
|
<control type="label">
|
||||||
|
<right>213</right>
|
||||||
|
<posy>35</posy>
|
||||||
|
<width>200</width>
|
||||||
|
<height>65</height>
|
||||||
|
<font>font12</font>
|
||||||
|
<align>right</align>
|
||||||
|
<aligny>center</aligny>
|
||||||
|
<textcolor>FFFFFFFF</textcolor>
|
||||||
|
<label>$INFO[System.Time]</label>
|
||||||
|
</control>
|
||||||
|
<control type="image">
|
||||||
|
<posx>153r</posx>
|
||||||
|
<posy>54</posy>
|
||||||
|
<width>93</width>
|
||||||
|
<height>30</height>
|
||||||
|
<texture>plugin.video.plexkodiconnect/home/plex.png</texture>
|
||||||
|
</control>
|
||||||
|
</control>
|
||||||
|
|
||||||
|
|
||||||
|
<control type="group">
|
||||||
|
<visible>!String.IsEmpty(Window.Property(busy))</visible>
|
||||||
|
<control type="image">
|
||||||
|
<posx>840</posx>
|
||||||
|
<posy>465</posy>
|
||||||
|
<width>240</width>
|
||||||
|
<height>150</height>
|
||||||
|
<texture>script.plex/busy-back.png</texture>
|
||||||
|
<colordiffuse>A0FFFFFF</colordiffuse>
|
||||||
|
</control>
|
||||||
|
<control type="image">
|
||||||
|
<posx>915</posx>
|
||||||
|
<posy>521</posy>
|
||||||
|
<width>90</width>
|
||||||
|
<height>38</height>
|
||||||
|
<texture diffuse="script.plex/busy-diffuse.png">script.plex/busy.gif</texture>
|
||||||
|
</control>
|
||||||
|
</control>
|
||||||
|
</controls>
|
||||||
|
</window>
|
Loading…
Reference in a new issue