Connection manager, part 2
This commit is contained in:
parent
962ce6da1e
commit
7a9e0611ed
9 changed files with 200 additions and 456 deletions
|
@ -1158,12 +1158,12 @@ msgstr ""
|
|||
|
||||
# Server selection dialog: button text to sign in or sign out of plex.tv
|
||||
msgctxt "#30600"
|
||||
msgid "Toggle plex.tv sign-in"
|
||||
msgid "Sign-in to plex.tv"
|
||||
msgstr ""
|
||||
|
||||
# Server selection dialog: button text to add server manually
|
||||
msgctxt "#30601"
|
||||
msgid "Manually add server"
|
||||
msgid "Manually add PMS"
|
||||
msgstr ""
|
||||
|
||||
# Button text, e.g. to cancel a dialog
|
||||
|
|
|
@ -56,6 +56,7 @@ class Monitor_Window(Thread):
|
|||
except:
|
||||
log.error('Failed to execute function %s with params %s'
|
||||
% (function, params))
|
||||
raise
|
||||
|
||||
def run(self):
|
||||
thread_stopped = self.thread_stopped
|
||||
|
|
|
@ -2,13 +2,21 @@
|
|||
###############################################################################
|
||||
from logging import getLogger
|
||||
from hashlib import md5
|
||||
import json
|
||||
import requests
|
||||
from struct import pack
|
||||
import socket
|
||||
import time
|
||||
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
|
||||
from utils import tryDecode
|
||||
from PlexFunctions import PMSHttpsEnabled
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -35,6 +43,11 @@ CONNECTIONMODE = {
|
|||
'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):
|
||||
|
||||
default_timeout = 20
|
||||
default_timeout = 30
|
||||
apiClients = []
|
||||
minServerVersion = "3.0.5930"
|
||||
minServerVersion = "1.7.0.0"
|
||||
connectUser = None
|
||||
# Token for plex.tv
|
||||
plexToken = None
|
||||
|
||||
def __init__(self, appName, appVersion, deviceName, deviceId,
|
||||
capabilities=None, devicePixelRatio=None):
|
||||
log.info("Begin ConnectionManager constructor")
|
||||
log.debug("Instantiating")
|
||||
|
||||
self.credentialProvider = cred.Credentials()
|
||||
self.appName = appName
|
||||
|
@ -144,8 +158,7 @@ class ConnectionManager(object):
|
|||
if server is None or systemInfo is None:
|
||||
return
|
||||
|
||||
server['Name'] = systemInfo['ServerName']
|
||||
server['Id'] = systemInfo['Id']
|
||||
server['Id'] = systemInfo.attrib['machineIdentifier']
|
||||
|
||||
if systemInfo.get('LocalAddress'):
|
||||
server['LocalAddress'] = systemInfo['LocalAddress']
|
||||
|
@ -155,16 +168,11 @@ class ConnectionManager(object):
|
|||
server['WakeOnLanInfos'] = [{'MacAddress': systemInfo['MacAddress']}]
|
||||
|
||||
def _getHeaders(self, request):
|
||||
|
||||
headers = request.setdefault('headers', {})
|
||||
|
||||
if request.get('dataType') == "json":
|
||||
headers['Accept'] = "application/json"
|
||||
request.pop('dataType')
|
||||
|
||||
headers['X-Application'] = self._addAppInfoToConnectRequest()
|
||||
headers['Content-type'] = request.get('contentType',
|
||||
'application/x-www-form-urlencoded; charset=UTF-8')
|
||||
headers['Accept'] = '*/*'
|
||||
headers['Content-type'] = request.get(
|
||||
'contentType',
|
||||
"application/x-www-form-urlencoded")
|
||||
|
||||
def requestUrl(self, request):
|
||||
|
||||
|
@ -192,9 +200,10 @@ class ConnectionManager(object):
|
|||
|
||||
else:
|
||||
try:
|
||||
return r.json()
|
||||
except ValueError:
|
||||
r.content # Read response to release connection
|
||||
return etree.fromstring(r.content)
|
||||
except etree.ParseError:
|
||||
# Read response to release connection
|
||||
r.content
|
||||
return
|
||||
|
||||
def _requests(self, action, **kwargs):
|
||||
|
@ -212,78 +221,67 @@ class ConnectionManager(object):
|
|||
def getConnectUrl(self, handler):
|
||||
return "https://connect.emby.media/service/%s" % handler
|
||||
|
||||
def _findServers(self, foundServers):
|
||||
|
||||
@staticmethod
|
||||
def _findServers(foundServers):
|
||||
servers = []
|
||||
|
||||
for foundServer in foundServers:
|
||||
|
||||
server = self._convertEndpointAddressToManualAddress(foundServer)
|
||||
|
||||
info = {
|
||||
'Id': foundServer['Id'],
|
||||
'LocalAddress': server or foundServer['Address'],
|
||||
'Name': foundServer['Name']
|
||||
}
|
||||
info['LastCONNECTIONMODE'] = CONNECTIONMODE['Manual'] if info.get('ManualAddress') else CONNECTIONMODE['Local']
|
||||
|
||||
servers.append(info)
|
||||
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:
|
||||
for server in foundServers:
|
||||
if '200 OK' not in server['data']:
|
||||
continue
|
||||
ip = server['from'][0]
|
||||
info = {'LastCONNECTIONMODE': CONNECTIONMODE['Local']}
|
||||
for line in server['data'].split('\n'):
|
||||
if line.startswith('Name:'):
|
||||
info['Name'] = tryDecode(line.split(':')[1].strip())
|
||||
elif line.startswith('Port:'):
|
||||
info['Port'] = line.split(':')[1].strip()
|
||||
elif line.startswith('Resource-Identifier:'):
|
||||
info['Id'] = line.split(':')[1].strip()
|
||||
elif line.startswith('Updated-At:'):
|
||||
pass
|
||||
|
||||
return None
|
||||
elif line.startswith('Version:'):
|
||||
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):
|
||||
"""
|
||||
PlexGDM
|
||||
"""
|
||||
# setup socket for discovery -> multicast message
|
||||
GDM = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
GDM.settimeout(2.0)
|
||||
|
||||
MULTI_GROUP = ("<broadcast>", 7359)
|
||||
MESSAGE = "who is EmbyServer?"
|
||||
|
||||
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)
|
||||
# Set the time-to-live for messages to 2 for local network
|
||||
ttl = pack('b', 2)
|
||||
GDM.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
|
||||
|
||||
servers = []
|
||||
|
||||
try:
|
||||
sock.sendto(MESSAGE, MULTI_GROUP)
|
||||
except Exception as error:
|
||||
log.error(error)
|
||||
return servers
|
||||
|
||||
# Send data to the multicast group
|
||||
GDM.sendto(MSG_PLEXGDM, (IP_PLEXGDM, PORT_PLEXGDM))
|
||||
# Look for responses from all recipients
|
||||
while True:
|
||||
try:
|
||||
data, addr = sock.recvfrom(1024) # buffer size
|
||||
servers.append(json.loads(data))
|
||||
|
||||
data, server = GDM.recvfrom(1024)
|
||||
servers.append({'from': server, 'data': data})
|
||||
except socket.timeout:
|
||||
log.info("Found Servers: %s" % servers)
|
||||
return servers
|
||||
|
||||
except Exception as e:
|
||||
log.error("Error trying to find servers: %s" % e)
|
||||
break
|
||||
except:
|
||||
# Probably error: (101, 'Network is unreachable')
|
||||
log.error('Could not find Plex servers using GDM')
|
||||
import traceback
|
||||
log.error("Traceback:\n%s" % traceback.format_exc())
|
||||
finally:
|
||||
GDM.close()
|
||||
return servers
|
||||
|
||||
def _normalizeAddress(self, address):
|
||||
|
@ -345,15 +343,11 @@ class ConnectionManager(object):
|
|||
self.credentialProvider.getCredentials(credentials)
|
||||
|
||||
def _tryConnect(self, url, timeout=None, options={}):
|
||||
|
||||
url = self.getEmbyServerUrl(url, "system/info/public")
|
||||
log.info("tryConnect url: %s" % url)
|
||||
|
||||
url = '%s/identity' % url
|
||||
log.debug("tryConnect url: %s" % url)
|
||||
return self.requestUrl({
|
||||
|
||||
'type': "GET",
|
||||
'url': url,
|
||||
'dataType': "json",
|
||||
'timeout': timeout,
|
||||
'ssl': options.get('ssl')
|
||||
})
|
||||
|
@ -361,6 +355,61 @@ class ConnectionManager(object):
|
|||
def _addAppInfoToConnectRequest(self):
|
||||
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):
|
||||
|
||||
log.info("Begin getConnectServers")
|
||||
|
@ -375,7 +424,6 @@ class ConnectionManager(object):
|
|||
|
||||
'type': "GET",
|
||||
'url': url,
|
||||
'dataType': "json",
|
||||
'headers': {
|
||||
'X-Connect-UserToken': credentials['ConnectAccessToken']
|
||||
}
|
||||
|
@ -396,18 +444,16 @@ class ConnectionManager(object):
|
|||
return servers
|
||||
|
||||
def getAvailableServers(self):
|
||||
|
||||
log.info("Begin getAvailableServers")
|
||||
|
||||
# Clone the array
|
||||
credentials = self.credentialProvider.getCredentials()
|
||||
|
||||
connectServers = self._getConnectServers(credentials)
|
||||
foundServers = self._findServers(self._serverDiscovery())
|
||||
|
||||
servers = list(credentials['Servers'])
|
||||
self._mergeServers(servers, foundServers)
|
||||
|
||||
if self.plexToken:
|
||||
connectServers = self.__get_PMS_servers_from_plex_tv()
|
||||
self._mergeServers(servers, connectServers)
|
||||
foundServers = self._findServers(self._serverDiscovery())
|
||||
self._mergeServers(servers, foundServers)
|
||||
|
||||
servers = self._filterServers(servers, connectServers)
|
||||
|
||||
|
@ -550,8 +596,9 @@ class ConnectionManager(object):
|
|||
return self._testNextCONNECTIONMODE(tests, index+1, server, options)
|
||||
else:
|
||||
|
||||
if self._compareVersions(self._getMinServerVersion(), result['Version']) == 1:
|
||||
log.warn("minServerVersion requirement not met. Server version: %s" % result['Version'])
|
||||
if self._compareVersions(self._getMinServerVersion(),
|
||||
result.attrib['version']) == 1:
|
||||
log.warn("minServerVersion requirement not met. Server version: %s" % result.attrib['version'])
|
||||
return {
|
||||
'State': CONNECTIONSTATE['ServerUpdateNeeded'],
|
||||
'Servers': [server]
|
||||
|
@ -616,7 +663,6 @@ class ConnectionManager(object):
|
|||
'type': "GET",
|
||||
'url': self.getEmbyServerUrl(url, "System/Info"),
|
||||
'ssl': options.get('ssl'),
|
||||
'dataType': "json",
|
||||
'headers': {
|
||||
'X-MediaBrowser-Token': server['AccessToken']
|
||||
}
|
||||
|
@ -631,7 +677,6 @@ class ConnectionManager(object):
|
|||
'type': "GET",
|
||||
'url': self.getEmbyServerUrl(url, "users/%s" % server['UserId']),
|
||||
'ssl': options.get('ssl'),
|
||||
'dataType': "json",
|
||||
'headers': {
|
||||
'X-MediaBrowser-Token': server['AccessToken']
|
||||
}
|
||||
|
@ -658,7 +703,6 @@ class ConnectionManager(object):
|
|||
'nameOrEmail': username,
|
||||
'password': md5
|
||||
},
|
||||
'dataType': "json"
|
||||
}
|
||||
try:
|
||||
result = self.requestUrl(request)
|
||||
|
@ -695,7 +739,6 @@ class ConnectionManager(object):
|
|||
|
||||
'type': "GET",
|
||||
'url': url,
|
||||
'dataType': "json",
|
||||
'headers': {
|
||||
'X-Connect-UserToken': accessToken
|
||||
}
|
||||
|
@ -718,7 +761,6 @@ class ConnectionManager(object):
|
|||
|
||||
'url': url,
|
||||
'type': "GET",
|
||||
'dataType': "json",
|
||||
'ssl': options.get('ssl'),
|
||||
'params': {
|
||||
'ConnectUserId': credentials['ConnectUserId']
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
from logging import getLogger
|
||||
from copy import deepcopy
|
||||
from os import makedirs
|
||||
|
||||
from xbmc import getIPAddress
|
||||
|
||||
# from connect.connectionmanager import ConnectionManager
|
||||
from downloadutils import DownloadUtils
|
||||
from dialogs.serverconnect import ServerConnect
|
||||
from dialogs.servermanual import ServerManual
|
||||
from connect.plex_tv import plex_tv_sign_in_with_pin
|
||||
import connect.connectionmanager as connectionmanager
|
||||
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, \
|
||||
check_connection
|
||||
import variables as v
|
||||
|
@ -18,6 +24,7 @@ import state
|
|||
|
||||
log = getLogger("PLEX."+__name__)
|
||||
|
||||
STATE = connectionmanager.CONNECTIONSTATE
|
||||
XML_PATH = (tryEncode(v.ADDON_PATH), "default", "1080i")
|
||||
|
||||
###############################################################################
|
||||
|
@ -55,6 +62,7 @@ def get_plex_login_from_settings():
|
|||
class ConnectManager(object):
|
||||
# Borg
|
||||
__shared_state = {}
|
||||
state = {}
|
||||
|
||||
def __init__(self):
|
||||
# Borg
|
||||
|
@ -68,19 +76,36 @@ class ConnectManager(object):
|
|||
plexdict = get_plex_login_from_settings()
|
||||
self.myplexlogin = plexdict['myplexlogin'] == 'true'
|
||||
self.plexLogin = plexdict['plexLogin']
|
||||
self.plexToken = plexdict['plexToken']
|
||||
self.plexid = plexdict['plexid']
|
||||
# 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.plexToken = plexdict['plexToken']
|
||||
self.__connect.plexToken = self.plexToken
|
||||
if self.plexToken:
|
||||
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):
|
||||
self.state = self.__connect.connect({'updateDateLastAccessed': False})
|
||||
return self.get_state()
|
||||
|
||||
def get_sate(self):
|
||||
window('emby_state.json', value=self.state)
|
||||
def get_state(self):
|
||||
state.CONNECT_STATE = deepcopy(self.state)
|
||||
return self.state
|
||||
|
||||
def get_server(self, server, options={}):
|
||||
|
@ -98,13 +123,14 @@ class ConnectManager(object):
|
|||
"""
|
||||
Will return selected server or raise RuntimeError
|
||||
"""
|
||||
status = self.__connect.connect({'enableAutoLogin': False})
|
||||
dia = ServerConnect("script-plex-connect-server.xml", *XML_PATH)
|
||||
kwargs = {
|
||||
'connect_manager': None, # self._connect
|
||||
'connect_manager': self.__connect,
|
||||
'username': state.PLEX_USERNAME,
|
||||
'user_image': state.PLEX_USER_IMAGE,
|
||||
# 'servers': state.get('Servers') or [],
|
||||
# 'emby_connect': False if user else True
|
||||
'servers': status.get('Servers') or [],
|
||||
'plex_connect': False if status.get('ConnectUser') else True
|
||||
}
|
||||
dia.set_args(**kwargs)
|
||||
dia.doModal()
|
||||
|
@ -113,12 +139,9 @@ class ConnectManager(object):
|
|||
log.debug("Server selected")
|
||||
return dia.get_server()
|
||||
|
||||
elif dia._is_connect_login():
|
||||
elif dia.is_connect_login():
|
||||
log.debug("Login to plex.tv")
|
||||
try:
|
||||
self._login_connect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.plex_tv_signin()
|
||||
return self.select_servers()
|
||||
|
||||
elif dia.is_manual_server():
|
||||
|
@ -134,7 +157,7 @@ class ConnectManager(object):
|
|||
def manual_server(self):
|
||||
# Return server or raise error
|
||||
dia = ServerManual("script-plex-connect-server-manual.xml", *XML_PATH)
|
||||
dia._set_connect_manager(self.__connect)
|
||||
dia.set_connect_manager(self.__connect)
|
||||
dia.doModal()
|
||||
|
||||
if dia._is_connected():
|
||||
|
@ -142,19 +165,6 @@ class ConnectManager(object):
|
|||
else:
|
||||
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):
|
||||
# Return user or raise error
|
||||
server = server or self.state['Servers'][0]
|
||||
|
@ -233,7 +243,7 @@ class ConnectManager(object):
|
|||
# Update the token in data.txt
|
||||
self.__connect.credentialProvider.getCredentials(credentials)
|
||||
|
||||
def _get_connect_servers(self):
|
||||
def get_connect_servers(self):
|
||||
|
||||
connect_servers = []
|
||||
servers = self.__connect.getAvailableServers()
|
||||
|
@ -580,8 +590,7 @@ class ConnectManager(object):
|
|||
"""
|
||||
Returns a list of servers from GDM and possibly plex.tv
|
||||
"""
|
||||
self.discoverPMS(xbmc.getIPAddress(),
|
||||
plexToken=self.plexToken)
|
||||
self.discoverPMS(getIPAddress(), plexToken=self.plexToken)
|
||||
serverlist = self.plx.returnServerList(self.plx.g_PMS)
|
||||
log.debug('PMS serverlist: %s' % serverlist)
|
||||
return serverlist
|
||||
|
|
|
@ -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')
|
|
@ -7,12 +7,14 @@ from logging import getLogger
|
|||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
import connect.connectionmanager as connectionmanager
|
||||
from utils import language as lang
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = getLogger("PLEX."+__name__)
|
||||
|
||||
CONN_STATE = connectionmanager.CONNECTIONSTATE
|
||||
ACTION_PARENT_DIR = 9
|
||||
ACTION_PREVIOUS_MENU = 10
|
||||
ACTION_BACK = 92
|
||||
|
@ -25,7 +27,7 @@ CANCEL = 201
|
|||
MESSAGE_BOX = 202
|
||||
MESSAGE = 203
|
||||
BUSY = 204
|
||||
EMBY_CONNECT = 205
|
||||
PLEX_CONNECT = 205
|
||||
MANUAL_SERVER = 206
|
||||
|
||||
###############################################################################
|
||||
|
@ -42,7 +44,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
|
|||
_manual_server = False
|
||||
|
||||
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():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
@ -74,8 +76,8 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
|
|||
if self.user_image is not None:
|
||||
self.getControl(USER_IMAGE).setImage(self.user_image)
|
||||
|
||||
if not self.emby_connect: # Change connect user
|
||||
self.getControl(EMBY_CONNECT).setLabel("[UPPERCASE][B]"+'plex.tv user change'+"[/B][/UPPERCASE]")
|
||||
if not self.plex_connect: # Change connect user
|
||||
self.getControl(PLEX_CONNECT).setLabel("[UPPERCASE][B]"+'plex.tv user change'+"[/B][/UPPERCASE]")
|
||||
|
||||
if self.servers:
|
||||
self.setFocus(self.list_)
|
||||
|
@ -107,7 +109,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
|
|||
|
||||
def onClick(self, control):
|
||||
|
||||
if control == EMBY_CONNECT:
|
||||
if control == PLEX_CONNECT:
|
||||
self.connect_manager.clearData()
|
||||
self._connect_login = True
|
||||
self.close()
|
||||
|
|
|
@ -38,3 +38,6 @@ PLEX_USER_ID = None
|
|||
# Token passed along, e.g. if playback initiated by Plex Companion. Might be
|
||||
# another user playing something! Token identifies user
|
||||
PLEX_TRANSIENT_TOKEN = None
|
||||
|
||||
# Used by connectmanager.py
|
||||
CONNECT_STATE = {}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import xbmc
|
||||
from xbmcaddon import Addon
|
||||
from os.path import join
|
||||
|
||||
# Paths are in unicode, otherwise Windows will throw fits
|
||||
# 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])
|
||||
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
|
||||
KODI_PROFILE = tryDecode(xbmc.translatePath("special://profile"))
|
||||
ADDON_PATH_DATA = join(KODI_PROFILE, 'addon_data', ADDON_ID, '')
|
||||
|
||||
if xbmc.getCondVisibility('system.platform.osx'):
|
||||
PLATFORM = "MacOSX"
|
||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue