diff --git a/resources/lib/PlexCompanion.py b/resources/lib/PlexCompanion.py index 1a2b8f3d..9c4281f4 100644 --- a/resources/lib/PlexCompanion.py +++ b/resources/lib/PlexCompanion.py @@ -7,22 +7,25 @@ import xbmc import clientinfo import utils -from plexbmchelper import listener, plexgdm, subscribers -from plexbmchelper.settings import settings +from plexbmchelper import listener, plexgdm, subscribers, functions, \ + httppersist @utils.logging +@utils.ThreadMethodsAdditionalSuspend('emby_serverStatus') @utils.ThreadMethods class PlexCompanion(threading.Thread): def __init__(self): - self.port = int(utils.settings('companionPort')) ci = clientinfo.ClientInfo() self.clientId = ci.getDeviceId() self.deviceName = ci.getDeviceName() - self.logMsg("----===## Starting PlexBMC Helper ##===----", 1) + + self.port = int(utils.settings('companionPort')) + self.logMsg("----===## Starting PlexCompanion ##===----", 1) # Start GDM for server/client discovery - self.client = plexgdm.plexgdm(debug=settings['gdm_debug']) + self.client = plexgdm.plexgdm( + debug=utils.settings('companionGDMDebugging')) self.client.clientDetails(self.clientId, # UUID self.deviceName, # clientName self.port, @@ -34,23 +37,38 @@ class PlexCompanion(threading.Thread): threading.Thread.__init__(self) def run(self): + # Cache for quicker while loops + log = self.logMsg + client = self.client + threadStopped = self.threadStopped + threadSuspended = self.threadSuspended start_count = 0 - window = utils.window + + # Start up instances + requestMgr = httppersist.RequestMgr() + jsonClass = functions.jsonClass(requestMgr) + subscriptionManager = subscribers.SubscriptionManager( + jsonClass, requestMgr) + + # Start up httpd while True: try: httpd = listener.ThreadedHTTPServer( + client, + subscriptionManager, + jsonClass, ('', self.port), listener.MyHandler) httpd.timeout = 0.95 break except: - self.logMsg("Unable to start PlexCompanion. Traceback:", -1) - self.logMsg(traceback.print_exc(), -1) + log("Unable to start PlexCompanion. Traceback:", -1) + log(traceback.print_exc(), -1) xbmc.sleep(3000) if start_count == 3: - self.logMsg("Error: Unable to start web helper.", -1) + log("Error: Unable to start web helper.", -1) httpd = False break @@ -59,15 +77,15 @@ class PlexCompanion(threading.Thread): if not httpd: return - self.client.start_all() + client.start_all() + message_count = 0 - is_running = False - while not self.threadStopped(): + while not threadStopped(): # If we are not authorized, sleep # Otherwise, we trigger a download which leads to a # re-authorizations - while self.threadSuspended() or window('emby_serverStatus'): - if self.threadStopped(): + while threadSuspended(): + if threadStopped(): break xbmc.sleep(1000) try: @@ -76,31 +94,29 @@ class PlexCompanion(threading.Thread): message_count += 1 if message_count > 100: - if self.client.check_client_registration(): - self.logMsg("Client is still registered", 1) + if client.check_client_registration(): + log("Client is still registered", 1) else: - self.logMsg("Client is no longer registered", 1) - self.logMsg("Plex Companion still running on port %s" - % self.port, 1) + log("Client is no longer registered", 1) + log("Plex Companion still running on port %s" + % self.port, 1) message_count = 0 - if not is_running: - self.logMsg("Plex Companion has started", 0) - is_running = True + # Get and set servers + subscriptionManager.serverlist = client.getServerList() - subscribers.subMgr.notify() - settings['serverList'] = self.client.getServerList() + subscriptionManager.notify() xbmc.sleep(50) except: - self.logMsg("Error in loop, continuing anyway", 1) - self.logMsg(traceback.format_exc(), 1) + log("Error in loop, continuing anyway", 1) + log(traceback.format_exc(), 1) xbmc.sleep(50) - self.client.stop_all() + client.stop_all() try: httpd.socket.shutdown(socket.SHUT_RDWR) except: pass finally: httpd.socket.close() - self.logMsg("----===## Plex Companion stopped ##===----", 0) + log("----===## Plex Companion stopped ##===----", 0) diff --git a/resources/lib/plexbmchelper/functions.py b/resources/lib/plexbmchelper/functions.py index 07ee7b72..2b00b109 100644 --- a/resources/lib/plexbmchelper/functions.py +++ b/resources/lib/plexbmchelper/functions.py @@ -1,26 +1,35 @@ import base64 -import inspect import json import string -import traceback import xbmc -from settings import settings -from httppersist import requests +import settings +from utils import logMsg + def xbmc_photo(): return "photo" + + def xbmc_video(): return "video" + + def xbmc_audio(): return "audio" + def plex_photo(): return "photo" + + def plex_video(): return "video" + + def plex_audio(): return "music" + def xbmc_type(plex_type): if plex_type == plex_photo(): return xbmc_photo() @@ -28,7 +37,8 @@ def xbmc_type(plex_type): return xbmc_video() elif plex_type == plex_audio(): return xbmc_audio() - + + def plex_type(xbmc_type): if xbmc_type == xbmc_photo(): return plex_photo() @@ -37,6 +47,7 @@ def plex_type(xbmc_type): elif xbmc_type == xbmc_audio(): return plex_audio() + def getPlatform(): if xbmc.getCondVisibility('system.platform.osx'): return "MacOSX" @@ -50,140 +61,23 @@ def getPlatform(): return "RaspberryPi" elif xbmc.getCondVisibility('system.platform.linux'): return "Linux" - elif xbmc.getCondVisibility('system.platform.android'): + elif xbmc.getCondVisibility('system.platform.android'): return "Android" return "Unknown" - -def printDebug( msg, functionname=True ): - if settings['debug']: - if functionname is False: - print str(msg) - else: - print "PleXBMC Helper -> " + inspect.stack()[1][3] + ": " + str(msg) - -""" communicate with XBMC """ -def jsonrpc(action, arguments = {}): - """ put some JSON together for the JSON-RPC APIv6 """ - if action.lower() == "sendkey": - request=json.dumps({ "jsonrpc" : "2.0" , "method" : "Input.SendText", "params" : { "text" : self.arguments[0], "done" : False }} ) - elif action.lower() == "ping": - request=json.dumps({ "jsonrpc" : "2.0", - "id" : 1 , - "method" : "JSONRPC.Ping" }) - elif action.lower() == "playmedia": - xbmc.Player().play("plugin://plugin.video.plexkodiconnect/" - "?mode=companion&arguments=%s" - % arguments) - return True - elif arguments: - request=json.dumps({ "id" : 1, - "jsonrpc" : "2.0", - "method" : action, - "params" : arguments}) - else: - request=json.dumps({ "id" : 1, - "jsonrpc" : "2.0", - "method" : action}) - - printDebug("Sending request to XBMC without network stack: %s" % request) - result = parseJSONRPC(xbmc.executeJSONRPC(request)) - if not result and settings['webserver_enabled']: - # xbmc.executeJSONRPC appears to fail on the login screen, but going - # through the network stack works, so let's try the request again - result = parseJSONRPC(requests.post( - "127.0.0.1", - settings['port'], - "/jsonrpc", - request, - { 'Content-Type' : 'application/json', - 'Authorization' : 'Basic ' + string.strip(base64.encodestring(settings['user'] + ':' + settings['passwd'])) })) - - return result - - - -def parseJSONRPC(jsonraw): - if not jsonraw: - printDebug("Empty response from XBMC") - return {} - else: - printDebug("Response from XBMC: %s" % jsonraw) - parsed=json.loads(jsonraw) - if parsed.get('error', False): - print "XBMC returned an error: %s" % parsed.get('error') - return parsed.get('result', {}) def getXMLHeader(): return ''+"\r\n" + def getOKMsg(): return getXMLHeader() + '' -def getPlexHeaders(): - h = { - "Content-type": "application/x-www-form-urlencoded", - "Access-Control-Allow-Origin": "*", - "X-Plex-Version": settings['version'], - "X-Plex-Client-Identifier": settings['uuid'], - "X-Plex-Provides": "player", - "X-Plex-Product": "PlexKodiConnect", - "X-Plex-Device-Name": settings['client_name'], - "X-Plex-Platform": "XBMC", - "X-Plex-Model": getPlatform(), - "X-Plex-Device": "PC", - } - if settings['myplex_user']: - h["X-Plex-Username"] = settings['myplex_user'] - return h - -def getServerByHost(host): - list = settings['serverList'] - if len(list) == 1: - return list[0] - for server in list: - if server.get('serverName') in host or server.get('server') in host: - return server - return {} - -def getPlayers(): - info = jsonrpc("Player.GetActivePlayers") or [] - ret = {} - for player in info: - player['playerid'] = int(player['playerid']) - ret[player['type']] = player - return ret - -def getPlayerIds(): - ret = [] - for player in getPlayers().values(): - ret.append(player['playerid']) - return ret - -def getVideoPlayerId(players = False): - if players is None: - players = getPlayers() - return players.get(xbmc_video(), {}).get('playerid', None) - -def getAudioPlayerId(players = False): - if players is None: - players = getPlayers() - return players.get(xbmc_audio(), {}).get('playerid', None) - -def getPhotoPlayerId(players = False): - if players is None: - players = getPlayers() - return players.get(xbmc_photo(), {}).get('playerid', None) - -def getVolume(): - answ = jsonrpc('Application.GetProperties', { "properties": [ "volume", 'muted' ] }) - vol = str(answ.get('volume', 100)) - mute = ("0", "1")[answ.get('muted', False)] - return (vol, mute) def timeToMillis(time): return (time['hours']*3600 + time['minutes']*60 + time['seconds'])*1000 + time['milliseconds'] + def millisToTime(t): millis = int(t) seconds = millis / 1000 @@ -194,5 +88,118 @@ def millisToTime(t): millis = millis % 1000 return {'hours':hours,'minutes':minutes,'seconds':seconds,'milliseconds':millis} + def textFromXml(element): - return element.firstChild.data \ No newline at end of file + return element.firstChild.data + + +class jsonClass(): + + def __init__(self, requestMgr): + self.settings = settings.getSettings() + self.requestMgr = requestMgr + + def jsonrpc(self, action, arguments={}): + """ put some JSON together for the JSON-RPC APIv6 """ + if action.lower() == "sendkey": + request=json.dumps({ "jsonrpc" : "2.0" , "method" : "Input.SendText", "params" : { "text" : arguments[0], "done" : False }} ) + elif action.lower() == "ping": + request=json.dumps({ "jsonrpc" : "2.0", + "id" : 1 , + "method" : "JSONRPC.Ping" }) + elif action.lower() == "playmedia": + xbmc.Player().play("plugin://plugin.video.plexkodiconnect/" + "?mode=companion&arguments=%s" + % arguments) + return True + elif arguments: + request=json.dumps({ "id" : 1, + "jsonrpc" : "2.0", + "method" : action, + "params" : arguments}) + else: + request=json.dumps({ "id" : 1, + "jsonrpc" : "2.0", + "method" : action}) + + logMsg("PlexCompanion", + "Sending request to XBMC without network stack: %s" + % request, 2) + result = self.parseJSONRPC(xbmc.executeJSONRPC(request)) + + if not result and self.settings['webserver_enabled']: + # xbmc.executeJSONRPC appears to fail on the login screen, but going + # through the network stack works, so let's try the request again + result = self.parseJSONRPC(self.requestMgr.post( + "127.0.0.1", + self.settings['port'], + "/jsonrpc", + request, + { 'Content-Type' : 'application/json', + 'Authorization' : 'Basic ' + string.strip(base64.encodestring(self.settings['user'] + ':' + self.settings['passwd'])) })) + + return result + + def getPlexHeaders(self): + h = { + "Content-type": "application/x-www-form-urlencoded", + "Access-Control-Allow-Origin": "*", + "X-Plex-Version": self.settings['version'], + "X-Plex-Client-Identifier": self.settings['uuid'], + "X-Plex-Provides": "player", + "X-Plex-Product": "PlexKodiConnect", + "X-Plex-Device-Name": self.settings['client_name'], + "X-Plex-Platform": "Kodi", + "X-Plex-Model": getPlatform(), + "X-Plex-Device": "PC", + } + if self.settings['myplex_user']: + h["X-Plex-Username"] = self.settings['myplex_user'] + return h + + def parseJSONRPC(self, jsonraw): + if not jsonraw: + logMsg("PlexCompanion", "Empty response from XBMC", 1) + return {} + else: + logMsg("PlexCompanion", "Response from XBMC: %s" % jsonraw, 2) + parsed=json.loads(jsonraw) + if parsed.get('error', False): + logMsg("PlexCompanion", "XBMC returned an error: %s" + % parsed.get('error'), -1) + return parsed.get('result', {}) + + def getPlayers(self): + info = self.jsonrpc("Player.GetActivePlayers") or [] + ret = {} + for player in info: + player['playerid'] = int(player['playerid']) + ret[player['type']] = player + return ret + + def getPlayerIds(self): + ret = [] + for player in self.getPlayers().values(): + ret.append(player['playerid']) + return ret + + def getVideoPlayerId(self, players=False): + if players is None: + players = self.getPlayers() + return players.get(xbmc_video(), {}).get('playerid', None) + + def getAudioPlayerId(self, players=False): + if players is None: + players = self.getPlayers() + return players.get(xbmc_audio(), {}).get('playerid', None) + + def getPhotoPlayerId(self, players=False): + if players is None: + players = self.getPlayers() + return players.get(xbmc_photo(), {}).get('playerid', None) + + def getVolume(self): + answ = self.jsonrpc('Application.GetProperties', { "properties": [ "volume", 'muted' ] }) + vol = str(answ.get('volume', 100)) + mute = ("0", "1")[answ.get('muted', False)] + return (vol, mute) diff --git a/resources/lib/plexbmchelper/httppersist.py b/resources/lib/plexbmchelper/httppersist.py index 8d8857f9..01e08a14 100644 --- a/resources/lib/plexbmchelper/httppersist.py +++ b/resources/lib/plexbmchelper/httppersist.py @@ -1,7 +1,12 @@ import httplib import traceback import string +import errno +from socket import error as socket_error +from utils import logging + +@logging class RequestMgr: def __init__(self): self.conns = {} @@ -9,24 +14,24 @@ class RequestMgr: def getConnection(self, protocol, host, port): conn = self.conns.get(protocol+host+str(port), False) if not conn: - if protocol=="https": + if protocol == "https": conn = httplib.HTTPSConnection(host, port) else: conn = httplib.HTTPConnection(host, port) self.conns[protocol+host+str(port)] = conn return conn - + def closeConnection(self, protocol, host, port): conn = self.conns.get(protocol+host+str(port), False) if conn: conn.close() self.conns.pop(protocol+host+str(port), None) - + def dumpConnections(self): for conn in self.conns.values(): conn.close() self.conns = {} - + def post(self, host, port, path, body, header={}, protocol="http"): conn = None try: @@ -35,27 +40,33 @@ class RequestMgr: conn.request("POST", path, body, header) data = conn.getresponse() if int(data.status) >= 400: - print "HTTP response error: " + str(data.status) - # this should return false, but I'm hacking it since iOS returns 404 no matter what + self.logMsg("HTTP response error: %s" % str(data.status), -1) + # this should return false, but I'm hacking it since iOS + # returns 404 no matter what return data.read() or True - else: + else: return data.read() or True - except: - print "Unable to connect to %s\nReason:" % host - traceback.print_exc() + except socket_error as serr: + # Ignore remote close and connection refused (e.g. shutdown PKC) + if serr.errno in (errno.WSAECONNABORTED, errno.WSAECONNREFUSED): + pass + else: + self.logMsg("Unable to connect to %s\nReason:" % host, -1) + self.logMsg(traceback.print_exc(), -1) self.conns.pop(protocol+host+str(port), None) if conn: conn.close() return False - - def getwithparams(self, host, port, path, params, header={}, protocol="http"): + + def getwithparams(self, host, port, path, params, header={}, + protocol="http"): newpath = path + '?' pairs = [] for key in params: pairs.append(str(key)+'='+str(params[key])) newpath += string.join(pairs, '&') return self.get(host, port, newpath, header, protocol) - + def get(self, host, port, path, header={}, protocol="http"): try: conn = self.getConnection(protocol, host, port) @@ -63,14 +74,13 @@ class RequestMgr: conn.request("GET", path, headers=header) data = conn.getresponse() if int(data.status) >= 400: - print "HTTP response error: " + str(data.status) + self.logMsg("HTTP response error: %s" % str(data.status), -1) return False - else: + else: return data.read() or True except: - print "Unable to connect to %s\nReason: %s" % (host, traceback.print_exc()) + self.logMsg("Unable to connect to %s\nReason:" % host, -1) + self.logMsg(traceback.print_exc(), -1) self.conns.pop(protocol+host+str(port), None) conn.close() return False - -requests = RequestMgr() diff --git a/resources/lib/plexbmchelper/listener.py b/resources/lib/plexbmchelper/listener.py index 2e53dcfb..b573f4c8 100644 --- a/resources/lib/plexbmchelper/listener.py +++ b/resources/lib/plexbmchelper/listener.py @@ -4,38 +4,56 @@ import xbmc from SocketServer import ThreadingMixIn from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from urlparse import urlparse, parse_qs -from settings import settings +import settings from functions import * -from subscribers import subMgr +from utils import logging + +@logging class MyHandler(BaseHTTPRequestHandler): protocol_version = 'HTTP/1.1' - def log_message(s, format, *args): - # I have my own logging, suppressing BaseHTTPRequestHandler's - #printDebug(format % args) - return True + + def __init__(self, *args, **kwargs): + self.serverlist = [] + self.settings = settings.getSettings() + BaseHTTPRequestHandler.__init__(self, *args, **kwargs) + + def getServerByHost(self, host): + if len(self.serverlist) == 1: + return self.serverlist[0] + for server in self.serverlist: + if (server.get('serverName') in host or + server.get('server') in host): + return server + return {} + def do_HEAD(s): - printDebug( "Serving HEAD request..." ) + s.logMsg("Serving HEAD request...", 2) s.answer_request(0) def do_GET(s): - printDebug( "Serving GET request..." ) + s.logMsg("Serving GET request...", 2) s.answer_request(1) def do_OPTIONS(s): s.send_response(200) s.send_header('Content-Length', '0') - s.send_header('X-Plex-Client-Identifier', settings['uuid']) + s.send_header('X-Plex-Client-Identifier', s.settings['uuid']) s.send_header('Content-Type', 'text/plain') s.send_header('Connection', 'close') s.send_header('Access-Control-Max-Age', '1209600') s.send_header('Access-Control-Allow-Origin', '*') - s.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT, HEAD') - s.send_header('Access-Control-Allow-Headers', 'x-plex-version, x-plex-platform-version, x-plex-username, x-plex-client-identifier, x-plex-target-client-identifier, x-plex-device-name, x-plex-platform, x-plex-product, accept, x-plex-device') + s.send_header('Access-Control-Allow-Methods', + 'POST, GET, OPTIONS, DELETE, PUT, HEAD') + s.send_header('Access-Control-Allow-Headers', + 'x-plex-version, x-plex-platform-version, ' + 'x-plex-username, x-plex-client-identifier, ' + 'x-plex-target-client-identifier, x-plex-device-name, ' + 'x-plex-platform, x-plex-product, accept, x-plex-device') s.end_headers() s.wfile.close() - def response(s, body, headers = {}, code = 200): + def response(s, body, headers={}, code=200): try: s.send_response(code) for key in headers: @@ -49,73 +67,76 @@ class MyHandler(BaseHTTPRequestHandler): pass def answer_request(s, sendData): + s.serverlist = s.server.client.getServerList() + s.subMgr = s.server.subscriptionManager + s.js = s.server.jsonClass try: - request_path=s.path[1:] - request_path=re.sub(r"\?.*","",request_path) + request_path = s.path[1:] + request_path = re.sub(r"\?.*", "", request_path) url = urlparse(s.path) paramarrays = parse_qs(url.query) params = {} for key in paramarrays: params[key] = paramarrays[key][0] - printDebug ( "request path is: [%s]" % ( request_path,) ) - printDebug ( "params are: %s" % params ) - subMgr.updateCommandID(s.headers.get('X-Plex-Client-Identifier', s.client_address[0]), params.get('commandID', False)) + s.logMsg("request path is: [%s]" % (request_path,), 2) + s.logMsg("params are: %s" % params, 2) + s.subMgr.updateCommandID(s.headers.get('X-Plex-Client-Identifier', s.client_address[0]), params.get('commandID', False)) if request_path=="version": - s.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % settings['version']) + s.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % s.settings['version']) elif request_path=="verify": - result=jsonrpc("ping") + result=s.js.jsonrpc("ping") s.response("XBMC JSON connection test:\r\n"+result) elif "resources" == request_path: resp = getXMLHeader() resp += "" resp += " self.discovery_interval: self.discover() discovery_count=0 - xbmc.sleep(1000) + xbmc.sleep(500) def start_discovery(self, daemon = False): if not self._discovery_is_running: diff --git a/resources/lib/plexbmchelper/settings.py b/resources/lib/plexbmchelper/settings.py index e722b565..fe494c7d 100644 --- a/resources/lib/plexbmchelper/settings.py +++ b/resources/lib/plexbmchelper/settings.py @@ -1,48 +1,46 @@ -import xbmc import xbmcaddon import utils -settings = {} - -guisettingsXML = utils.guisettingsXML() - def getGUI(name): + guisettingsXML = utils.guisettingsXML() try: ans = list(guisettingsXML.iter(name))[0].text if ans is None: ans = '' - return ans except: - return "" + ans = '' + return ans -addon = xbmcaddon.Addon() -plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect') -settings['debug'] = utils.settings('companionDebugging') -settings['gdm_debug'] = utils.settings('companionGDMDebugging') +def getSettings(): + settings = {} + addon = xbmcaddon.Addon() + plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect') -# Transform 'true' into True because of the way Kodi's file settings work -kodiSettingsList = ['debug', 'gdm_debug'] -for entry in kodiSettingsList: - if settings[entry] == 'true': - settings[entry] = True - else: - settings[entry] = False + settings['debug'] = utils.settings('companionDebugging') + settings['gdm_debug'] = utils.settings('companionGDMDebugging') -settings['client_name'] = plexbmc.getSetting('deviceName') + # Transform 'true' into True because of the way Kodi's file settings work + kodiSettingsList = ['debug', 'gdm_debug'] + for entry in kodiSettingsList: + if settings[entry] == 'true': + settings[entry] = True + else: + settings[entry] = False -# XBMC web server settings -xbmc.sleep(5000) -settings['webserver_enabled'] = (getGUI('webserver') == "true") -settings['port'] = int(getGUI('webserverport')) -settings['user'] = getGUI('webserverusername') -settings['passwd'] = getGUI('webserverpassword') + settings['client_name'] = plexbmc.getSetting('deviceName') -settings['uuid'] = plexbmc.getSetting('plex_client_Id') + # XBMC web server settings + settings['webserver_enabled'] = (getGUI('webserver') == "true") + settings['port'] = int(getGUI('webserverport')) + settings['user'] = getGUI('webserverusername') + settings['passwd'] = getGUI('webserverpassword') -settings['version'] = plexbmc.getAddonInfo('version') -settings['plexbmc_version'] = plexbmc.getAddonInfo('version') -settings['myplex_user'] = plexbmc.getSetting('username') -settings['serverList'] = [] -settings['myport'] = addon.getSetting('companionPort') + settings['uuid'] = plexbmc.getSetting('plex_client_Id') + + settings['version'] = plexbmc.getAddonInfo('version') + settings['plexbmc_version'] = plexbmc.getAddonInfo('version') + settings['myplex_user'] = plexbmc.getSetting('username') + settings['myport'] = addon.getSetting('companionPort') + return settings diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index c85238c7..b742a21d 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -1,18 +1,17 @@ import re import threading -# from xml.dom.minidom import parseString from functions import * -from settings import settings -from httppersist import requests from xbmc import Player -# import xbmcgui import downloadutils -from utils import window +from utils import window, logging import PlexFunctions as pf + +@logging class SubscriptionManager: - def __init__(self): + def __init__(self, jsonClass, RequestMgr): + self.serverlist = [] self.subscribers = {} self.info = {} self.lastkey = "" @@ -27,8 +26,20 @@ class SubscriptionManager: self.download = downloadutils.DownloadUtils() self.xbmcplayer = Player() + self.js = jsonClass + self.RequestMgr = RequestMgr + + def getServerByHost(self, host): + if len(self.serverlist) == 1: + return self.serverlist[0] + for server in self.serverlist: + if (server.get('serverName') in host or + server.get('server') in host): + return server + return {} + def getVolume(self): - self.volume, self.mute = getVolume() + self.volume, self.mute = self.js.getVolume() def msg(self, players): msg = getXMLHeader() @@ -45,9 +56,9 @@ class SubscriptionManager: else: self.mainlocation = "navigation" msg += ' location="%s">' % self.mainlocation - msg += self.getTimelineXML(getAudioPlayerId(players), plex_audio()) - msg += self.getTimelineXML(getPhotoPlayerId(players), plex_photo()) - msg += self.getTimelineXML(getVideoPlayerId(players), plex_video()) + msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio()) + msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo()) + msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video()) msg += "\r\n" return msg @@ -88,7 +99,7 @@ class SubscriptionManager: ret += ' location="%s"' % (self.mainlocation) ret += ' key="%s"' % (self.lastkey) ret += ' ratingKey="%s"' % (self.lastratingkey) - serv = getServerByHost(self.server) + serv = self.getServerByHost(self.server) if info.get('playQueueID'): self.containerKey = "/playQueues/%s" % info.get('playQueueID') ret += ' playQueueID="%s"' % info.get('playQueueID') @@ -124,7 +135,7 @@ class SubscriptionManager: def notify(self, event = False): self.cleanup() - players = getPlayers() + players = self.js.getPlayers() # fetch the message, subscribers or not, since the server # will need the info anyway msg = self.msg(players) @@ -152,32 +163,39 @@ class SubscriptionManager: params['state'] = info['state'] params['time'] = info['time'] params['duration'] = info['duration'] - serv = getServerByHost(self.server) + serv = self.getServerByHost(self.server) url = serv.get('protocol', 'http') + '://' \ + serv.get('server', 'localhost') + ':' \ + serv.get('port', '32400') + "/:/timeline" self.download.downloadUrl(url, type="GET", parameters=params) # requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http')) - printDebug("params: %s" % params) - printDebug("players: %s" % players) - printDebug("sent server notification with state = %s" % params['state']) + self.logMsg("params: %s" % params, 2) + self.logMsg("players: %s" % players, 2) + self.logMsg("sent server notification with state = %s" + % params['state'], 2) def controllable(self): return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause" - + def addSubscriber(self, protocol, host, port, uuid, commandID): - sub = Subscriber(protocol, host, port, uuid, commandID) + sub = Subscriber(protocol, + host, + port, + uuid, + commandID, + self, + self.RequestMgr) with threading.RLock(): self.subscribers[sub.uuid] = sub return sub - + def removeSubscriber(self, uuid): with threading.RLock(): for sub in self.subscribers.values(): if sub.uuid == uuid or sub.host == uuid: sub.cleanup() del self.subscribers[sub.uuid] - + def cleanup(self): with threading.RLock(): for sub in self.subscribers.values(): @@ -189,8 +207,8 @@ class SubscriptionManager: info = {} try: # get info from the player - props = jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled", "repeat"]}) - printDebug(jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]})) + props = self.js.jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled", "repeat"]}) + self.logMsg(self.js.jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]}), 2) info['time'] = timeToMillis(props['time']) info['duration'] = timeToMillis(props['totaltime']) info['state'] = ("paused", "playing")[int(props['speed'])] @@ -214,8 +232,11 @@ class SubscriptionManager: return info + +@logging class Subscriber: - def __init__(self, protocol, host, port, uuid, commandID): + def __init__(self, protocol, host, port, uuid, commandID, + subMgr, RequestMgr): self.protocol = protocol or "http" self.host = host self.port = port or 32400 @@ -224,12 +245,18 @@ class Subscriber: self.navlocationsent = False self.age = 0 self.download = downloadutils.DownloadUtils() + self.subMgr = subMgr + self.RequestMgr = RequestMgr + def __eq__(self, other): return self.uuid == other.uuid + def tostr(self): return "uuid=%s,commandID=%i" % (self.uuid, self.commandID) + def cleanup(self): - requests.closeConnection(self.protocol, self.host, self.port) + self.RequestMgr.closeConnection(self.protocol, self.host, self.port) + def send_update(self, msg, is_nav): self.age += 1 if not is_nav: @@ -239,7 +266,8 @@ class Subscriber: else: self.navlocationsent = True msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg) - printDebug("sending xml to subscriber %s: %s" % (self.tostr(), msg)) + self.logMsg("sending xml to subscriber %s: %s" + % (self.tostr(), msg), 2) url = self.protocol + '://' + self.host + ':' + self.port \ + "/:/timeline" t = threading.Thread(target=self.threadedSend, args=(url, msg)) @@ -255,6 +283,4 @@ class Subscriber: postBody=msg, type="POSTXML") if response in [False, None, 401]: - subMgr.removeSubscriber(self.uuid) - -subMgr = SubscriptionManager() + self.subMgr.removeSubscriber(self.uuid) diff --git a/service.py b/service.py index 68465035..6749450e 100644 --- a/service.py +++ b/service.py @@ -322,8 +322,7 @@ class Service(): delay = int(utils.settings('startupDelay')) xbmc.log("Delaying Plex startup by: %s sec..." % delay) -# Plex: add 10 seconds just for good measure -if delay and xbmc.Monitor().waitForAbort(delay+10): +if delay and xbmc.Monitor().waitForAbort(delay): # Start the service xbmc.log("Abort requested while waiting. Emby for kodi not started.") else: