Connection manager, part 2

This commit is contained in:
tomkat83 2017-07-16 15:22:08 +02:00
parent 962ce6da1e
commit 7a9e0611ed
9 changed files with 200 additions and 456 deletions

View file

@ -1158,12 +1158,12 @@ msgstr ""
# Server selection dialog: button text to sign in or sign out of plex.tv # Server selection dialog: button text to sign in or sign out of plex.tv
msgctxt "#30600" msgctxt "#30600"
msgid "Toggle plex.tv sign-in" msgid "Sign-in to plex.tv"
msgstr "" msgstr ""
# Server selection dialog: button text to add server manually # Server selection dialog: button text to add server manually
msgctxt "#30601" msgctxt "#30601"
msgid "Manually add server" msgid "Manually add PMS"
msgstr "" msgstr ""
# Button text, e.g. to cancel a dialog # Button text, e.g. to cancel a dialog

View file

@ -56,6 +56,7 @@ class Monitor_Window(Thread):
except: except:
log.error('Failed to execute function %s with params %s' log.error('Failed to execute function %s with params %s'
% (function, params)) % (function, params))
raise
def run(self): def run(self):
thread_stopped = self.thread_stopped thread_stopped = self.thread_stopped

View file

@ -2,13 +2,21 @@
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
from hashlib import md5 from hashlib import md5
import json
import requests import requests
from struct import pack
import socket import socket
import time import time
from datetime import datetime from datetime import datetime
import xml.etree.ElementTree as etree
from Queue import Queue
from threading import Thread
from xbmc import sleep
import credentials as cred import credentials as cred
from utils import tryDecode
from PlexFunctions import PMSHttpsEnabled
############################################################################### ###############################################################################
@ -35,6 +43,11 @@ CONNECTIONMODE = {
'Manual': 2 'Manual': 2
} }
# multicast to PMS
IP_PLEXGDM = '239.0.0.250'
PORT_PLEXGDM = 32414
MSG_PLEXGDM = 'M-SEARCH * HTTP/1.0'
############################################################################### ###############################################################################
@ -52,15 +65,16 @@ def getServerAddress(server, mode):
class ConnectionManager(object): class ConnectionManager(object):
default_timeout = 30
default_timeout = 20
apiClients = [] apiClients = []
minServerVersion = "3.0.5930" minServerVersion = "1.7.0.0"
connectUser = None connectUser = None
# Token for plex.tv
plexToken = None
def __init__(self, appName, appVersion, deviceName, deviceId, def __init__(self, appName, appVersion, deviceName, deviceId,
capabilities=None, devicePixelRatio=None): capabilities=None, devicePixelRatio=None):
log.info("Begin ConnectionManager constructor") log.debug("Instantiating")
self.credentialProvider = cred.Credentials() self.credentialProvider = cred.Credentials()
self.appName = appName self.appName = appName
@ -144,8 +158,7 @@ class ConnectionManager(object):
if server is None or systemInfo is None: if server is None or systemInfo is None:
return return
server['Name'] = systemInfo['ServerName'] server['Id'] = systemInfo.attrib['machineIdentifier']
server['Id'] = systemInfo['Id']
if systemInfo.get('LocalAddress'): if systemInfo.get('LocalAddress'):
server['LocalAddress'] = systemInfo['LocalAddress'] server['LocalAddress'] = systemInfo['LocalAddress']
@ -155,16 +168,11 @@ class ConnectionManager(object):
server['WakeOnLanInfos'] = [{'MacAddress': systemInfo['MacAddress']}] server['WakeOnLanInfos'] = [{'MacAddress': systemInfo['MacAddress']}]
def _getHeaders(self, request): def _getHeaders(self, request):
headers = request.setdefault('headers', {}) headers = request.setdefault('headers', {})
headers['Accept'] = '*/*'
if request.get('dataType') == "json": headers['Content-type'] = request.get(
headers['Accept'] = "application/json" 'contentType',
request.pop('dataType') "application/x-www-form-urlencoded")
headers['X-Application'] = self._addAppInfoToConnectRequest()
headers['Content-type'] = request.get('contentType',
'application/x-www-form-urlencoded; charset=UTF-8')
def requestUrl(self, request): def requestUrl(self, request):
@ -192,9 +200,10 @@ class ConnectionManager(object):
else: else:
try: try:
return r.json() return etree.fromstring(r.content)
except ValueError: except etree.ParseError:
r.content # Read response to release connection # Read response to release connection
r.content
return return
def _requests(self, action, **kwargs): def _requests(self, action, **kwargs):
@ -212,79 +221,68 @@ class ConnectionManager(object):
def getConnectUrl(self, handler): def getConnectUrl(self, handler):
return "https://connect.emby.media/service/%s" % handler return "https://connect.emby.media/service/%s" % handler
def _findServers(self, foundServers): @staticmethod
def _findServers(foundServers):
servers = [] servers = []
for server in foundServers:
for foundServer in foundServers: if '200 OK' not in server['data']:
continue
server = self._convertEndpointAddressToManualAddress(foundServer) ip = server['from'][0]
info = {'LastCONNECTIONMODE': CONNECTIONMODE['Local']}
info = { for line in server['data'].split('\n'):
'Id': foundServer['Id'], if line.startswith('Name:'):
'LocalAddress': server or foundServer['Address'], info['Name'] = tryDecode(line.split(':')[1].strip())
'Name': foundServer['Name'] elif line.startswith('Port:'):
} info['Port'] = line.split(':')[1].strip()
info['LastCONNECTIONMODE'] = CONNECTIONMODE['Manual'] if info.get('ManualAddress') else CONNECTIONMODE['Local'] elif line.startswith('Resource-Identifier:'):
info['Id'] = line.split(':')[1].strip()
servers.append(info) elif line.startswith('Updated-At:'):
else:
return servers
def _convertEndpointAddressToManualAddress(self, info):
if info.get('Address') and info.get('EndpointAddress'):
address = info['EndpointAddress'].split(':')[0]
# Determine the port, if any
parts = info['Address'].split(':')
if len(parts) > 1:
portString = parts[len(parts)-1]
try:
address += ":%s" % int(portString)
return self._normalizeAddress(address)
except ValueError:
pass pass
elif line.startswith('Version:'):
return None pass
# Need to check whether we need HTTPS or only HTTP
https = PMSHttpsEnabled('%s:%s' % (ip, info['Port']))
if https is None:
# Error contacting url. Skip for now
continue
elif https is True:
info['LocalAddress'] = 'https://%s:%s' % (ip, info['Port'])
else:
info['LocalAddress'] = 'http://%s:%s' % (ip, info['Port'])
servers.append(info)
return servers
def _serverDiscovery(self): def _serverDiscovery(self):
"""
PlexGDM
"""
# setup socket for discovery -> multicast message
GDM = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
GDM.settimeout(2.0)
MULTI_GROUP = ("<broadcast>", 7359) # Set the time-to-live for messages to 2 for local network
MESSAGE = "who is EmbyServer?" ttl = pack('b', 2)
GDM.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(1.0) # This controls the socket.timeout exception
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
log.debug("MultiGroup : %s" % str(MULTI_GROUP))
log.debug("Sending UDP Data: %s" % MESSAGE)
servers = [] servers = []
try: try:
sock.sendto(MESSAGE, MULTI_GROUP) # Send data to the multicast group
except Exception as error: GDM.sendto(MSG_PLEXGDM, (IP_PLEXGDM, PORT_PLEXGDM))
log.error(error) # Look for responses from all recipients
return servers while True:
try:
while True: data, server = GDM.recvfrom(1024)
try: servers.append({'from': server, 'data': data})
data, addr = sock.recvfrom(1024) # buffer size except socket.timeout:
servers.append(json.loads(data)) break
except:
except socket.timeout: # Probably error: (101, 'Network is unreachable')
log.info("Found Servers: %s" % servers) log.error('Could not find Plex servers using GDM')
return servers import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
except Exception as e: finally:
log.error("Error trying to find servers: %s" % e) GDM.close()
return servers return servers
def _normalizeAddress(self, address): def _normalizeAddress(self, address):
# Attempt to correct bad input # Attempt to correct bad input
@ -345,15 +343,11 @@ class ConnectionManager(object):
self.credentialProvider.getCredentials(credentials) self.credentialProvider.getCredentials(credentials)
def _tryConnect(self, url, timeout=None, options={}): def _tryConnect(self, url, timeout=None, options={}):
url = '%s/identity' % url
url = self.getEmbyServerUrl(url, "system/info/public") log.debug("tryConnect url: %s" % url)
log.info("tryConnect url: %s" % url)
return self.requestUrl({ return self.requestUrl({
'type': "GET", 'type': "GET",
'url': url, 'url': url,
'dataType': "json",
'timeout': timeout, 'timeout': timeout,
'ssl': options.get('ssl') 'ssl': options.get('ssl')
}) })
@ -361,6 +355,61 @@ class ConnectionManager(object):
def _addAppInfoToConnectRequest(self): def _addAppInfoToConnectRequest(self):
return "%s/%s" % (self.appName, self.appVersion) return "%s/%s" % (self.appName, self.appVersion)
def __get_PMS_servers_from_plex_tv(self):
"""
Retrieves Plex Media Servers from plex.tv/pms/resources
"""
servers = []
xml = self.requestUrl({
'url': 'https://plex.tv/api/resources?includeHttps=1',
'type': 'GET',
'headers': {'X-Plex-Token': self.plexToken},
'timeout': 5.0,
'ssl': True})
try:
xml.attrib
except AttributeError:
log.error('Could not get list of PMS from plex.tv')
return servers
maxAgeSeconds = 2*60*60*24
for device in xml.findall('Device'):
if 'server' not in device.attrib.get('provides'):
# No PMS - skip
continue
cons = device.find('Connection')
if cons is None:
# no valid connection - skip
continue
# check MyPlex data age - skip if >2 days
server = {'Name': device.attrib.get('name')}
infoAge = time.time() - int(device.attrib.get('lastSeenAt'))
if infoAge > maxAgeSeconds:
log.info("Server %s not seen for 2 days - skipping."
% server['Name'])
continue
server['Id'] = device.attrib['clientIdentifier']
server['ConnectServerId'] = device.attrib['clientIdentifier']
# server['AccessToken'] = device.attrib['accessToken']
server['ExchangeToken'] = device.attrib['accessToken']
# One's own Plex home?
server['UserLinkType'] = 'Guest' if device.attrib['owned'] == '0' \
else 'LinkedUser'
# Foreign PMS' user name
server['UserId'] = device.attrib.get('sourceTitle')
for con in cons:
if con.attrib['local'] == '1':
# Local LAN address; there might be several!!
server['LocalAddress'] = con.attrib['uri']
else:
server['RemoteAddress'] = con.attrib['uri']
# Additional stuff, not yet implemented
server['local'] = device.attrib.get('publicAddressMatches')
servers.append(server)
return servers
def _getConnectServers(self, credentials): def _getConnectServers(self, credentials):
log.info("Begin getConnectServers") log.info("Begin getConnectServers")
@ -375,7 +424,6 @@ class ConnectionManager(object):
'type': "GET", 'type': "GET",
'url': url, 'url': url,
'dataType': "json",
'headers': { 'headers': {
'X-Connect-UserToken': credentials['ConnectAccessToken'] 'X-Connect-UserToken': credentials['ConnectAccessToken']
} }
@ -396,18 +444,16 @@ class ConnectionManager(object):
return servers return servers
def getAvailableServers(self): def getAvailableServers(self):
log.info("Begin getAvailableServers") log.info("Begin getAvailableServers")
# Clone the array
credentials = self.credentialProvider.getCredentials() credentials = self.credentialProvider.getCredentials()
connectServers = self._getConnectServers(credentials)
foundServers = self._findServers(self._serverDiscovery())
servers = list(credentials['Servers']) servers = list(credentials['Servers'])
if self.plexToken:
connectServers = self.__get_PMS_servers_from_plex_tv()
self._mergeServers(servers, connectServers)
foundServers = self._findServers(self._serverDiscovery())
self._mergeServers(servers, foundServers) self._mergeServers(servers, foundServers)
self._mergeServers(servers, connectServers)
servers = self._filterServers(servers, connectServers) servers = self._filterServers(servers, connectServers)
@ -550,8 +596,9 @@ class ConnectionManager(object):
return self._testNextCONNECTIONMODE(tests, index+1, server, options) return self._testNextCONNECTIONMODE(tests, index+1, server, options)
else: else:
if self._compareVersions(self._getMinServerVersion(), result['Version']) == 1: if self._compareVersions(self._getMinServerVersion(),
log.warn("minServerVersion requirement not met. Server version: %s" % result['Version']) result.attrib['version']) == 1:
log.warn("minServerVersion requirement not met. Server version: %s" % result.attrib['version'])
return { return {
'State': CONNECTIONSTATE['ServerUpdateNeeded'], 'State': CONNECTIONSTATE['ServerUpdateNeeded'],
'Servers': [server] 'Servers': [server]
@ -616,7 +663,6 @@ class ConnectionManager(object):
'type': "GET", 'type': "GET",
'url': self.getEmbyServerUrl(url, "System/Info"), 'url': self.getEmbyServerUrl(url, "System/Info"),
'ssl': options.get('ssl'), 'ssl': options.get('ssl'),
'dataType': "json",
'headers': { 'headers': {
'X-MediaBrowser-Token': server['AccessToken'] 'X-MediaBrowser-Token': server['AccessToken']
} }
@ -631,7 +677,6 @@ class ConnectionManager(object):
'type': "GET", 'type': "GET",
'url': self.getEmbyServerUrl(url, "users/%s" % server['UserId']), 'url': self.getEmbyServerUrl(url, "users/%s" % server['UserId']),
'ssl': options.get('ssl'), 'ssl': options.get('ssl'),
'dataType': "json",
'headers': { 'headers': {
'X-MediaBrowser-Token': server['AccessToken'] 'X-MediaBrowser-Token': server['AccessToken']
} }
@ -658,7 +703,6 @@ class ConnectionManager(object):
'nameOrEmail': username, 'nameOrEmail': username,
'password': md5 'password': md5
}, },
'dataType': "json"
} }
try: try:
result = self.requestUrl(request) result = self.requestUrl(request)
@ -695,7 +739,6 @@ class ConnectionManager(object):
'type': "GET", 'type': "GET",
'url': url, 'url': url,
'dataType': "json",
'headers': { 'headers': {
'X-Connect-UserToken': accessToken 'X-Connect-UserToken': accessToken
} }
@ -718,7 +761,6 @@ class ConnectionManager(object):
'url': url, 'url': url,
'type': "GET", 'type': "GET",
'dataType': "json",
'ssl': options.get('ssl'), 'ssl': options.get('ssl'),
'params': { 'params': {
'ConnectUserId': credentials['ConnectUserId'] 'ConnectUserId': credentials['ConnectUserId']

View file

@ -1,14 +1,20 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
from copy import deepcopy
from os import makedirs
from xbmc import getIPAddress
# from connect.connectionmanager import ConnectionManager # from connect.connectionmanager import ConnectionManager
from downloadutils import DownloadUtils from downloadutils import DownloadUtils
from dialogs.serverconnect import ServerConnect from dialogs.serverconnect import ServerConnect
from dialogs.servermanual import ServerManual from dialogs.servermanual import ServerManual
from connect.plex_tv import plex_tv_sign_in_with_pin from connect.plex_tv import plex_tv_sign_in_with_pin
import connect.connectionmanager as connectionmanager
from userclient import UserClient from userclient import UserClient
from utils import window, settings, tryEncode, language as lang, dialog from utils import window, settings, tryEncode, language as lang, dialog, \
exists_dir
from PlexFunctions import GetMachineIdentifier, get_pms_settings, \ from PlexFunctions import GetMachineIdentifier, get_pms_settings, \
check_connection check_connection
import variables as v import variables as v
@ -18,6 +24,7 @@ import state
log = getLogger("PLEX."+__name__) log = getLogger("PLEX."+__name__)
STATE = connectionmanager.CONNECTIONSTATE
XML_PATH = (tryEncode(v.ADDON_PATH), "default", "1080i") XML_PATH = (tryEncode(v.ADDON_PATH), "default", "1080i")
############################################################################### ###############################################################################
@ -55,6 +62,7 @@ def get_plex_login_from_settings():
class ConnectManager(object): class ConnectManager(object):
# Borg # Borg
__shared_state = {} __shared_state = {}
state = {}
def __init__(self): def __init__(self):
# Borg # Borg
@ -68,19 +76,36 @@ class ConnectManager(object):
plexdict = get_plex_login_from_settings() plexdict = get_plex_login_from_settings()
self.myplexlogin = plexdict['myplexlogin'] == 'true' self.myplexlogin = plexdict['myplexlogin'] == 'true'
self.plexLogin = plexdict['plexLogin'] self.plexLogin = plexdict['plexLogin']
self.plexToken = plexdict['plexToken']
self.plexid = plexdict['plexid'] self.plexid = plexdict['plexid']
# Token for the PMS, not plex.tv # Token for the PMS, not plex.tv
self.__connect = connectionmanager.ConnectionManager(
appName="Kodi",
appVersion=v.ADDON_VERSION,
deviceName=v.DEVICENAME,
deviceId=window('plex_client_Id'))
self.pms_token = settings('accessToken') self.pms_token = settings('accessToken')
self.plexToken = plexdict['plexToken']
self.__connect.plexToken = self.plexToken
if self.plexToken: if self.plexToken:
log.debug('Found a plex.tv token in the settings') log.debug('Found a plex.tv token in the settings')
if not exists_dir(v.ADDON_PATH_DATA):
makedirs(v.ADDON_PATH_DATA)
self.__connect.setFilePath(v.ADDON_PATH_DATA)
if state.CONNECT_STATE:
self.state = state.CONNECT_STATE
else:
self.state = self.__connect.connect()
log.debug("Started with: %s", self.state)
state.CONNECT_STATE = deepcopy(self.state)
def update_state(self): def update_state(self):
self.state = self.__connect.connect({'updateDateLastAccessed': False}) self.state = self.__connect.connect({'updateDateLastAccessed': False})
return self.get_state() return self.get_state()
def get_sate(self): def get_state(self):
window('emby_state.json', value=self.state) state.CONNECT_STATE = deepcopy(self.state)
return self.state return self.state
def get_server(self, server, options={}): def get_server(self, server, options={}):
@ -98,13 +123,14 @@ class ConnectManager(object):
""" """
Will return selected server or raise RuntimeError Will return selected server or raise RuntimeError
""" """
status = self.__connect.connect({'enableAutoLogin': False})
dia = ServerConnect("script-plex-connect-server.xml", *XML_PATH) dia = ServerConnect("script-plex-connect-server.xml", *XML_PATH)
kwargs = { kwargs = {
'connect_manager': None, # self._connect 'connect_manager': self.__connect,
'username': state.PLEX_USERNAME, 'username': state.PLEX_USERNAME,
'user_image': state.PLEX_USER_IMAGE, 'user_image': state.PLEX_USER_IMAGE,
# 'servers': state.get('Servers') or [], 'servers': status.get('Servers') or [],
# 'emby_connect': False if user else True 'plex_connect': False if status.get('ConnectUser') else True
} }
dia.set_args(**kwargs) dia.set_args(**kwargs)
dia.doModal() dia.doModal()
@ -113,12 +139,9 @@ class ConnectManager(object):
log.debug("Server selected") log.debug("Server selected")
return dia.get_server() return dia.get_server()
elif dia._is_connect_login(): elif dia.is_connect_login():
log.debug("Login to plex.tv") log.debug("Login to plex.tv")
try: self.plex_tv_signin()
self._login_connect()
except RuntimeError:
pass
return self.select_servers() return self.select_servers()
elif dia.is_manual_server(): elif dia.is_manual_server():
@ -134,7 +157,7 @@ class ConnectManager(object):
def manual_server(self): def manual_server(self):
# Return server or raise error # Return server or raise error
dia = ServerManual("script-plex-connect-server-manual.xml", *XML_PATH) dia = ServerManual("script-plex-connect-server-manual.xml", *XML_PATH)
dia._set_connect_manager(self.__connect) dia.set_connect_manager(self.__connect)
dia.doModal() dia.doModal()
if dia._is_connected(): if dia._is_connected():
@ -142,19 +165,6 @@ class ConnectManager(object):
else: else:
raise RuntimeError("Server is not connected") raise RuntimeError("Server is not connected")
def _login_connect(self):
# Return connect user or raise error
dia = LoginConnect("script-emby-connect-login.xml", *XML_PATH)
dia._set_connect_manager(self.__connect)
dia.doModal()
self.update_state()
if dia.is_logged_in():
return dia.get_user()
else:
raise RuntimeError("Connect user is not logged in")
def login(self, server=None): def login(self, server=None):
# Return user or raise error # Return user or raise error
server = server or self.state['Servers'][0] server = server or self.state['Servers'][0]
@ -233,7 +243,7 @@ class ConnectManager(object):
# Update the token in data.txt # Update the token in data.txt
self.__connect.credentialProvider.getCredentials(credentials) self.__connect.credentialProvider.getCredentials(credentials)
def _get_connect_servers(self): def get_connect_servers(self):
connect_servers = [] connect_servers = []
servers = self.__connect.getAvailableServers() servers = self.__connect.getAvailableServers()
@ -580,8 +590,7 @@ class ConnectManager(object):
""" """
Returns a list of servers from GDM and possibly plex.tv Returns a list of servers from GDM and possibly plex.tv
""" """
self.discoverPMS(xbmc.getIPAddress(), self.discoverPMS(getIPAddress(), plexToken=self.plexToken)
plexToken=self.plexToken)
serverlist = self.plx.returnServerList(self.plx.g_PMS) serverlist = self.plx.returnServerList(self.plx.g_PMS)
log.debug('PMS serverlist: %s' % serverlist) log.debug('PMS serverlist: %s' % serverlist)
return serverlist return serverlist

View file

@ -1,136 +0,0 @@
# -*- coding: utf-8 -*-
##################################################################################################
import logging
import os
import xbmcgui
import xbmcaddon
from utils import language as lang
##################################################################################################
log = logging.getLogger("EMBY."+__name__)
addon = xbmcaddon.Addon('plugin.video.emby')
ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92
SIGN_IN = 200
CANCEL = 201
ERROR_TOGGLE = 202
ERROR_MSG = 203
ERROR = {
'Invalid': 1,
'Empty': 2
}
##################################################################################################
class LoginConnect(xbmcgui.WindowXMLDialog):
_user = None
error = None
def __init__(self, *args, **kwargs):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
def set_connect_manager(self, connect_manager):
self.connect_manager = connect_manager
def is_logged_in(self):
return True if self._user else False
def get_user(self):
return self._user
def onInit(self):
self.user_field = self._add_editcontrol(725, 385, 40, 500)
self.setFocus(self.user_field)
self.password_field = self._add_editcontrol(725, 470, 40, 500, password=1)
self.signin_button = self.getControl(SIGN_IN)
self.remind_button = self.getControl(CANCEL)
self.error_toggle = self.getControl(ERROR_TOGGLE)
self.error_msg = self.getControl(ERROR_MSG)
self.user_field.controlUp(self.remind_button)
self.user_field.controlDown(self.password_field)
self.password_field.controlUp(self.user_field)
self.password_field.controlDown(self.signin_button)
self.signin_button.controlUp(self.password_field)
self.remind_button.controlDown(self.user_field)
def onClick(self, control):
if control == SIGN_IN:
# Sign in to emby connect
self._disable_error()
user = self.user_field.getText()
password = self.password_field.getText()
if not user or not password:
# Display error
self._error(ERROR['Empty'], lang(30608))
log.error("Username or password cannot be null")
elif self._login(user, password):
self.close()
elif control == CANCEL:
# Remind me later
self.close()
def onAction(self, action):
if (self.error == ERROR['Empty']
and self.user_field.getText() and self.password_field.getText()):
self._disable_error()
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
self.close()
def _add_editcontrol(self, x, y, height, width, password=0):
media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
control = xbmcgui.ControlEdit(0, 0, 0, 0,
label="User",
font="font10",
textColor="ff525252",
focusTexture=os.path.join(media, "button-focus.png"),
noFocusTexture=os.path.join(media, "button-focus.png"),
isPassword=password)
control.setPosition(x, y)
control.setHeight(height)
control.setWidth(width)
self.addControl(control)
return control
def _login(self, username, password):
result = self.connect_manager.loginToConnect(username, password)
if result is False:
self._error(ERROR['Invalid'], lang(33009))
return False
else:
self._user = result
return True
def _error(self, state, message):
self.error = state
self.error_msg.setLabel(message)
self.error_toggle.setVisibleCondition('True')
def _disable_error(self):
self.error = None
self.error_toggle.setVisibleCondition('False')

View file

@ -7,12 +7,14 @@ from logging import getLogger
import xbmc import xbmc
import xbmcgui import xbmcgui
import connect.connectionmanager as connectionmanager
from utils import language as lang from utils import language as lang
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) log = getLogger("PLEX."+__name__)
CONN_STATE = connectionmanager.CONNECTIONSTATE
ACTION_PARENT_DIR = 9 ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10 ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92 ACTION_BACK = 92
@ -25,7 +27,7 @@ CANCEL = 201
MESSAGE_BOX = 202 MESSAGE_BOX = 202
MESSAGE = 203 MESSAGE = 203
BUSY = 204 BUSY = 204
EMBY_CONNECT = 205 PLEX_CONNECT = 205
MANUAL_SERVER = 206 MANUAL_SERVER = 206
############################################################################### ###############################################################################
@ -42,7 +44,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
_manual_server = False _manual_server = False
def set_args(self, **kwargs): def set_args(self, **kwargs):
# connect_manager, username, user_image, servers, emby_connect # connect_manager, username, user_image, servers, plex_connect
for key, value in kwargs.iteritems(): for key, value in kwargs.iteritems():
setattr(self, key, value) setattr(self, key, value)
@ -74,8 +76,8 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
if self.user_image is not None: if self.user_image is not None:
self.getControl(USER_IMAGE).setImage(self.user_image) self.getControl(USER_IMAGE).setImage(self.user_image)
if not self.emby_connect: # Change connect user if not self.plex_connect: # Change connect user
self.getControl(EMBY_CONNECT).setLabel("[UPPERCASE][B]"+'plex.tv user change'+"[/B][/UPPERCASE]") self.getControl(PLEX_CONNECT).setLabel("[UPPERCASE][B]"+'plex.tv user change'+"[/B][/UPPERCASE]")
if self.servers: if self.servers:
self.setFocus(self.list_) self.setFocus(self.list_)
@ -107,7 +109,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
def onClick(self, control): def onClick(self, control):
if control == EMBY_CONNECT: if control == PLEX_CONNECT:
self.connect_manager.clearData() self.connect_manager.clearData()
self._connect_login = True self._connect_login = True
self.close() self.close()

View file

@ -38,3 +38,6 @@ PLEX_USER_ID = None
# Token passed along, e.g. if playback initiated by Plex Companion. Might be # Token passed along, e.g. if playback initiated by Plex Companion. Might be
# another user playing something! Token identifies user # another user playing something! Token identifies user
PLEX_TRANSIENT_TOKEN = None PLEX_TRANSIENT_TOKEN = None
# Used by connectmanager.py
CONNECT_STATE = {}

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import xbmc import xbmc
from xbmcaddon import Addon from xbmcaddon import Addon
from os.path import join
# Paths are in unicode, otherwise Windows will throw fits # Paths are in unicode, otherwise Windows will throw fits
# For any file operations with KODI function, use encoded strings! # For any file operations with KODI function, use encoded strings!
@ -32,6 +33,7 @@ KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion') KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
KODI_PROFILE = tryDecode(xbmc.translatePath("special://profile")) KODI_PROFILE = tryDecode(xbmc.translatePath("special://profile"))
ADDON_PATH_DATA = join(KODI_PROFILE, 'addon_data', ADDON_ID, '')
if xbmc.getCondVisibility('system.platform.osx'): if xbmc.getCondVisibility('system.platform.osx'):
PLATFORM = "MacOSX" PLATFORM = "MacOSX"

View file

@ -1,179 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<window>
<defaultcontrol always="true">200</defaultcontrol>
<zorder>0</zorder>
<include>dialogeffect</include>
<controls>
<control type="image">
<description>Background fade</description>
<width>100%</width>
<height>100%</height>
<texture>emby-bg-fade.png</texture>
</control>
<control type="group">
<width>600</width>
<left>35%</left>
<top>15%</top>
<control type="image">
<description>Background box</description>
<texture colordiffuse="ff111111">white.png</texture>
<width>600</width>
<height>700</height>
</control>
<control type="group" id="202">
<top>705</top>
<visible>False</visible>
<control type="image">
<description>Error box</description>
<texture colordiffuse="ff222222">white.png</texture>
<width>100%</width>
<height>50</height>
</control>
<control type="label" id="203">
<description>Error message</description>
<textcolor>white</textcolor>
<font>font10</font>
<aligny>center</aligny>
<align>center</align>
<height>50</height>
</control>
</control>
<control type="image">
<description>Emby logo</description>
<texture>logo-white.png</texture>
<width>160</width>
<height>49</height>
<top>30</top>
<left>25</left>
</control>
<control type="group">
<width>500</width>
<left>50</left>
<control type="label">
<description>Sign in emby connect</description>
<label>$ADDON[plugin.video.emby 30600]</label>
<textcolor>white</textcolor>
<font>font12</font>
<aligny>top</aligny>
<top>115</top>
</control>
<control type="group">
<top>190</top>
<control type="label">
<description>Username email</description>
<label>$ADDON[plugin.video.emby 30543]</label>
<textcolor>ffa6a6a6</textcolor>
<font>font10</font>
<aligny>top</aligny>
</control>
<control type="image">
<description>separator</description>
<width>102%</width>
<height>0.5</height>
<top>66</top>
<left>-10</left>
<texture colordiffuse="ff525252" border="90,3,90,3">emby-separator.png</texture>
</control>
</control>
<control type="group">
<description>Password</description>
<top>275</top>
<control type="label">
<description>Password label</description>
<label>$ADDON[plugin.video.emby 30602]</label>
<textcolor>ffa6a6a6</textcolor>
<font>font10</font>
<aligny>top</aligny>
</control>
<control type="image">
<description>separator</description>
<width>102%</width>
<height>0.5</height>
<top>66</top>
<left>-10</left>
<texture colordiffuse="ff525252" border="90,3,90,3">emby-separator.png</texture>
</control>
</control>
<control type="group">
<description>Buttons</description>
<top>385</top>
<control type="button" id="200">
<description>Sign in</description>
<texturenofocus border="5" colordiffuse="ff0b8628">box.png</texturenofocus>
<texturefocus border="5" colordiffuse="ff13a134">box.png</texturefocus>
<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30605][/B][/UPPERCASE]</label>
<font>font10</font>
<textcolor>ffa6a6a6</textcolor>
<focusedcolor>white</focusedcolor>
<align>center</align>
<width>100%</width>
<height>50</height>
<ondown>201</ondown>
</control>
<control type="button" id="201">
<description>Cancel</description>
<texturenofocus border="5" colordiffuse="ff464646">box.png</texturenofocus>
<texturefocus border="5" colordiffuse="ff525252">box.png</texturefocus>
<label>[UPPERCASE][B]$ADDON[plugin.video.emby 30606][/B][/UPPERCASE]</label>
<font>font10</font>
<textcolor>ffa6a6a6</textcolor>
<focusedcolor>white</focusedcolor>
<align>center</align>
<width>100%</width>
<height>50</height>
<top>55</top>
<onup>200</onup>
</control>
</control>
<control type="group">
<description>Disclaimer</description>
<top>510</top>
<control type="label">
<description>Disclaimer label</description>
<label>$ADDON[plugin.video.emby 30603]</label>
<font>font10</font>
<textcolor>ff464646</textcolor>
<wrapmultiline>true</wrapmultiline>
<aligny>top</aligny>
<width>340</width>
<height>100%</height>
</control>
<control type="group">
<control type="label">
<description>Scan me</description>
<label>[UPPERCASE]$ADDON[plugin.video.emby 30604][/UPPERCASE]</label>
<font>font12</font>
<textcolor>ff0b8628</textcolor>
<aligny>top</aligny>
<width>200</width>
<top>120</top>
<left>230</left>
</control>
<control type="image">
<description>qrcode</description>
<texture>qrcode_disclaimer.png</texture>
<width>140</width>
<height>140</height>
<top>10</top>
<left>360</left>
</control>
</control>
</control>
</control>
</control>
</controls>
</window>